2014-11-21 1 views
2

멀티 스레드 환경을 처리하려고 시도한 Form이 있다고 가정 해 보겠습니다. 그것은 어떤 UI 수정이 완료되기 전에 UI 스레드에서 실행중인 경우에 따라서 검사 :비동기 메서드 내에서 InvokeRequired UI 패턴을 구현하려면 어떻게해야합니까?

partial class SomeForm : Form 
{ 
    public void DoSomethingToUserInterface() 
    { 
     if (InvokeRequired) 
     { 
      BeginInvoke(delegate { DoSomethingToUserInterface() }); 
     } 
     else 
     { 
      … // do the actual work (e.g. manipulate the form or its elements) 
     } 
    } 
} 
이제

의 내가 그 방법의 일부 내부의 긴 작업을 수행하고 가정 해 봅시다; 따라서 async/await을 사용하여 비동기로 만들고 싶습니다.

void 대신 Task (예외가 catch 될 수 있음)을 반환하도록 메서드 서명을 변경해야한다고 가정하면 BeginInvoke을 수행하는 부분을 어떻게 구현할 수 있습니까? 무엇을 반환해야합니까?

public async Task DoSomethingToUserInterfaceAsync() 
{ 
    if (InvokeRequired) 
    { 
     // what do I put here? 
    } 
    { 
     … // like before (but can now use `await` expressions) 
    } 
} 
+0

Btw., 나는 긴 연산을 추출하고, 일단 완료되면, 그 결과를'doSomethingToUserInterface'에 인수로 전달합니다. DoSomethingToUserInterface는 더 이상 'async'가 될 필요가 없습니다 (따라서 여기서 제시된 문제를 피할 수 있습니다). 그래도 여전히 솔루션에 관심이 있습니다. – stakx

+4

일반적으로,'async' /'await'는'Invoke()'에 대한 필요성을 제거해야합니다. 나는. GUI가 아닌 스레드 컨텍스트에서 이러한 메서드를 호출하는 상황에서 이전에는 어디에서나 이러한 코드를 사용하지 않도록 코드를 구성 할 수 있어야합니다. 따라서 여기서 메소드 서명을 변경하는 방법을 묻기보다는 호출자를 변경하여 처음부터 호출 할 필요가 없도록해야합니다. –

+0

@PeterDuniho : 합리적인 제안 인 것으로 보입니다. 그래서 기본적으로'DoSomethingToUserInterfaceAsync' (Windows Forms UI 이벤트 핸들러 일 가능성이있는 메소드까지) 호출 체인을 추적해야하며, 이러한 호출자 메소드가 모두'async/await'를 사용하도록 만드시겠습니까? – stakx

답변

1

당신은 (UI 스레드의 경우) 현재 동기화 컨텍스트에 대한 작업 스케줄러를 얻을 수 TaskScheduler.FromCurrentSynchronizationContext을 사용하고 나중에 사용하기 위해 필드에 저장할 수 있습니다.

UI 스레드에서 작업을 시작하려면 uiScheduler 메서드를 StartNew 메서드에 전달해야합니다. 그러면 TPL에서 제공된 스케줄러 (이 경우 UI 스레드)에서 작업을 예약합니다.

어쨌든 UI 스레드에서 물건을 실행하기로 결정 했으므로 UIScheduler에 일정을 예약하기 만하면 InvokeRequired을 확인할 필요가 없습니다. TaskScheduler.FromCurrentSynchronizationContext는 UI 스레드에서 호출해야합니다 것이 매우 중요합니다, 그렇지 않으면 발생합니다 예외 :

public async Task DoSomethingToUserInterfaceAsync() 
{ 
    await Task.Factory.StartNew(() => DoSomethingToUserInterface(), CancellationToken.None, TaskCreationOptions.None, uiScheduler); 
    ... 
} 

UI가 스케줄러를 검색하려면 다음 코드

private TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

주를 사용할 수 있습니다 또는 다른 사람 SynchronizationContext에 대해 TaskScheduler을 받으면 필요한 것을 수행하지 않습니다.

또한 UI 스레드 자체에서 비동기 작업을 시작한 경우 위의 마술이 필요하지 않습니다. 그것이 시작된 맥락에서 기다릴 것이다.

+0

'FromCurrentSynchronizationContext'는 UI 스레드에서 메소드 호출이 실행 중이면'WinFormsSynchronizationContext' 만 제공합니다. 'BeginInvoke'가 필요하다면 (사실이라면) 그는 UI 쓰레드에 있지 않고 아마도'ThreadPoolSynchronizationContext'를 얻을 것입니다. –

+0

@YuvalItzchakov는 대답의 첫 줄을 읽었습니다. * UI 작업 스케줄러 * –

+0

을 저장해야합니다. 그것은 아마도 많은 것을 설명하지 않으므로 오해의 소지가 있습니다. UI 스레드에서 실행되는 동안 동기화 컨텍스트를 저장해야한다는 사실에 대해 자세히 설명해야합니다. –

3

과 같은 맞춤 awaiterasync-await을 사용하는 경우 SynchronizationContext이 내재적으로 캡처됩니다. 나중에 비동기 메서드가 완료되면 뒤에 오는 모든 코드가 SynchronizationContext.Post을 사용하여 동일한 동기화 컨텍스트로 마샬링됩니다.

이렇게하면 InvokeRequired 및 UI 스레드에서 작업을 강화하는 데 사용되는 다른 기술을 사용할 필요가 없습니다. 이를 수행하기 위해서는 메소드 호출을 추적하여 최상위 레벨 메소드로 호출해야하고 아마도 async-await을 사용하도록 리펙토링해야합니다.

은 그러나, 같이 특정 문제를 해결하기 위해, 당신이 할 수있는 일은 캡처입니다 WinFormSynchronizationContext 당신의 Form는 초기화 할 때 :

partial class SomeForm : Form 
{ 
    private TaskScheduler _uiTaskScheduler; 
    public SomeForm() 
    { 
     _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 
    } 
} 

을 그리고 당신은 await로 할 때 나중에 사용

if (InvokeRequired) 
{ 
    Task uiTask = new Task(() => DoSomethingToUserInterface()); 
    uiTask.RunSynchronously(_uiTaskScheduler); 
} 
else 
{ 
    // Do async work 
} 
+1

먼저, .NET은 이미 귀하를 위해 그것을 수행합니다. Invoke가 실제로 필요한 경우 나머지 코드에는 버그가 있습니다. 둘째, SynchronizationContext.포스트는 작업을 포함하지 않고 컨텍스트에서 델리게이트를 실행하는 더 간단한 방법입니다. –

+0

@PanagiotisKanavos 'SynchronizationContext.Post'에 대한 호출을'대기 '할 수 없습니다. 후자가 더 좋다고 주장합니까? 나는 그렇게 생각하지 않는다. –

+0

두 번째 부분에 대해, 당신 말이 맞습니다. 'RunSynchronously'는 단지'SyncContext.Post'에 대한 래퍼 일뿐입니다. 첫 부분에 대해서,'Invoke'가 필요한 경우 왜 코드에 버그가 있습니까? –

관련 문제