UI 스레드에서 작업 계속


214

초기 작업이 생성 된 스레드에서 작업 계속을 실행하도록 지정하는 '표준'방법이 있습니까?

현재 아래 코드가 있습니다. 작동하지만 디스패처를 추적하고 두 번째 액션을 만드는 것은 불필요한 오버 헤드처럼 보입니다.

dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
    DoLongRunningWork();
});

Task UITask= task.ContinueWith(() =>
{
    dispatcher.Invoke(new Action(() =>
    {
        this.TextBlock1.Text = "Complete"; 
    }
});

예를 들어,을 사용할 수 있습니다 Control.Invoke(Action). TextBlock1.Invoke오히려dispatcher.Invoke
패닉 대령

2
@ColonelPanic에게 감사하지만 winforms가 아닌 WPF (태그가있는)를 사용하고있었습니다.
Greg Sansom

답변:


352

다음과 같이 연속을 호출하십시오 TaskScheduler.FromCurrentSynchronizationContext().

    Task UITask= task.ContinueWith(() =>
    {
     this.TextBlock1.Text = "Complete"; 
    }, TaskScheduler.FromCurrentSynchronizationContext());

현재 실행 컨텍스트가 UI 스레드에있는 경우에만 적합합니다.


39
현재 실행 컨텍스트가 UI 스레드에있는 경우에만 유효합니다. 다른 작업 내에서이 코드를 넣을 경우에, 당신은 InvalidOperationException이 (봐 얻을 예외 섹션)
stukselbax

3
.NET 4.5에서 Johan Larsson의 답변은 UI 스레드에서 작업을 계속하기위한 표준 방법으로 사용해야합니다. 다음과 같이 작성하십시오 : Await Task.Run (DoLongRunningWork); this.TextBlock1.Text = "완료"; 또한보십시오 : blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Marcel W

1
내 목숨을 구해줘 서 고마워 await / ContinueWith 내에서 주요 스레드를 호출하는 방법을 알아내는 데 몇 시간을 소비합니다. 다른 모든 사람에게 Unity 용 Google Firebase SDK를 사용하는 방법 과 여전히 동일한 문제가있는 경우 이는 효과적인 방법입니다.
CHaP

2
@MarcelW- await좋은 패턴이지만 async컨텍스트 내에있는 경우 (예 : 선언 된 메소드 async) 그렇지 않은 경우 여전히이 답변과 같은 작업을 수행해야합니다.
ToolmakerSteve

33

비동기를 사용하면 다음과 같이 할 수 있습니다.

await Task.Run(() => do some stuff);
// continue doing stuff on the same context as before.
// while it is the default it is nice to be explicit about it with:
await Task.Run(() => do some stuff).ConfigureAwait(true);

하나:

await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.

2
false버전 아래의 의견은 혼란 스럽습니다. 다른 스레드 false에서 계속 될 수 있다고 생각 했습니다 .
ToolmakerSteve

1
@ToolmakerSteve 생각하고있는 스레드에 따라 다릅니다. Task.Run에서 사용하는 작업자 스레드 또는 호출자 스레드? "작업이 완료된 동일한 스레드"는 작업자 스레드 (스레드 간 '전환'을 피함)를 의미합니다. 또한 ConfigureAwait (true)는 제어가 동일한 스레드로 리턴되고 동일한 컨텍스트 로만 리턴한다고 보장하지 않습니다 (차별이 중요하지는 않지만).
Max Barraclough

@MaxBarraclough-고마워요, "같은 스레드"가 무엇인지 잘못 읽었습니다. "일부 작업 수행"작업을 수행하기 위해 실행중인 스레드를 사용하여 성능을 최대화한다는 의미에서 스레드 간 전환을 피 함으로써이를 명확하게 해줍니다.
ToolmakerSteve

1
질문은 async메소드 내부에 있음을 지정하지 않습니다 (필요한 경우 사용 await). await사용할 수없는 경우 답변은 무엇입니까 ?
ToolmakerSteve

22

반환 값이 있으면 UI로 보내야하며 다음과 같이 일반 버전을 사용할 수 있습니다.

필자의 경우 MVVM ViewModel에서 호출됩니다.

var updateManifest = Task<ShippingManifest>.Run(() =>
    {
        Thread.Sleep(5000);  // prove it's really working!

        // GenerateManifest calls service and returns 'ShippingManifest' object 
        return GenerateManifest();  
    })

    .ContinueWith(manifest =>
    {
        // MVVM property
        this.ShippingManifest = manifest.Result;

        // or if you are not using MVVM...
        // txtShippingManifest.Text = manifest.Result.ToString();    

        System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now);

    }, TaskScheduler.FromCurrentSynchronizationContext());

GenerateManifest가 오타하기 전에 =를 추측하고 있습니다.
Sebastien F.

예-이제 사라졌습니다! 고마워.
Simon_Weaver

11

이 유용한 스레드이기 때문에이 버전을 추가하고 싶었고 이것이 매우 간단한 구현이라고 생각합니다. 멀티 스레드 응용 프로그램 인 경우이 유형을 여러 유형으로 여러 번 사용했습니다.

 Task.Factory.StartNew(() =>
      {
        DoLongRunningWork();
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
              { txt.Text = "Complete"; }));
      });

2
이 시나리오는 일부 시나리오에서 실행 가능한 솔루션이므로 다운 봇이 아닙니다. 그러나 허용되는 답변이 더 좋습니다. TaskSchedulerBCL의 일부인 기술 과 무관하며 Dispatcher화재 및 잊어 버린 비동기 작업 (예 :)에 대해 걱정할 필요가 없으므로 복잡한 일련의 작업을 구성하는 데 사용할 수 있습니다 BeginInvoke.
Kirill Shlenskiy

@Kirill은 일부 SO 스레드가 WinForms의 WPF를 사용하는 경우 디스패처를 만장일치로 올바른 방법으로 만장일치로 선언했기 때문에 약간 확장 할 수 있습니다. GUI 업데이트를 위해 백그라운드 스레드를 차단하고 싶지 않기 때문에 사용됩니다. FromCurrentSynchronizationContext는 디스패처와 동일한 방식으로 연속 태스크를 기본 스레드 메시지 큐에 넣지 않습니까?
Dean

1
그렇습니다. 그러나 OP는 확실히 WPF에 대해 묻고 태그를 지정하고 Dispatcher에 대한 참조를 유지하고 싶지 않습니다 (동기화 컨텍스트 중 하나를 가정합니다-메인 스레드에서만 가져와야합니다) 어딘가에 참조를 저장하십시오). 이것이 내가 게시 한 솔루션을 좋아하는 이유입니다. 스레드 안전 정적 참조가 내장되어 있어이 중 어느 것도 필요하지 않습니다. WPF 컨텍스트에서 이것이 매우 유용하다고 생각합니다.
Dean

3
방금 마지막 주석을 강화하고 싶었습니다. 개발자는 동기화 컨텍스트를 저장해야 할뿐만 아니라 주 스레드에서만 사용할 수 있다는 것을 알아야합니다. 이 문제는 수십 개의 SO 질문에서 혼란의 원인이었습니다. 사람들은 항상 작업자 스레드에서 그것을 얻으려고합니다. 코드 자체가 작업자 스레드로 이동 된 경우이 문제로 인해 실패합니다. 따라서 WPF의 보급으로 인해이 인기있는 질문에서이를 명확히해야합니다.
Dean

1
그럼에도 불구하고 코드가 주 스레드에 없을 수있는 경우 동기화 컨텍스트를 추적해야하는 [허용 된 답변]에 대한 Dean의 관찰은 주목해야하며,이를 피하는 것이이 답변의 이점입니다.
ToolmakerSteve

1

Task.Run 호출에 들어간 후 ui 스레드에서 작업을 수행하는 좋은 방법을 찾고 있었기 때문에 Google을 통해 여기에 도달했습니다. 다음 코드를 사용 await하면 UI 스레드로 다시 돌아갈 수 있습니다 .

나는 이것이 누군가를 돕기를 바랍니다.

public static class UI
{
    public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}

public struct DispatcherAwaiter : INotifyCompletion
{
    public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();

    public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);

    public void GetResult() { }

    public DispatcherAwaiter GetAwaiter()
    {
        return this;
    }
}

용법:

... code which is executed on the background thread...
await UI.Thread;
... code which will be run in the application dispatcher (ui thread) ...


매우 영리한! 그러나 직관적이지 않습니다. static수업 을 하는 것이 좋습니다 UI.
Theodor Zoulias
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.