UIスレッドでのタスクの継続


214

最初のタスクが作成されたスレッドでタスクの継続を実行するように指定する「標準」の方法はありますか?

現在、私は以下のコードを持っています-それは機能していますが、ディスパッチャーを追跡し、2番目のアクションを作成することは、不要なオーバーヘッドのように思えます。

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 = "Complete"; 参照:blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Marcel W

1
私の命を救うためのTHX。私はawait / ContinueWith内でメインスレッドを呼び出す方法を理解するために何時間も費やしています。Unity向けのGoogle Firebase SDKを使用している他のすべての人にとって、同じ問題がまだある場合、これは有効なアプローチです。
CHaP

2
@MarcelW-適切awaitなパターンですが、asyncコンテキスト内(宣言されたメソッドなど)内にいる場合のみですasync。そうでない場合でも、この答えのようなことをする必要があります。
ToolmakerSteve

33

asyncを使用すると、次のことを実行できます。

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 2018年

1
@ToolmakerSteve考えているスレッドに依存します。Task.Runによって使用されるワーカースレッド、または呼び出し側スレッド?「タスクが終了したのと同じスレッド」とは、ワーカースレッドを意味することに注意してください(スレッド間の「切り替え」を避けます)。また、ConfigureAwait(true)は、制御が同じスレッドに戻ることを保証するのではなく、同じコンテキストにのみ戻ることを保証します(区別は重要ではない場合があります)。
Max Barraclough

@MaxBarraclough-ありがとう、「同じスレッド」の意味を誤解しました。実行中のスレッドを使用してパフォーマンスを最大化するという意味でのスレッド間の切り替えを回避することで、「 "何かを行う"タスクを実行する」ことがわかります。
ToolmakerSteve 2018年

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.15年

はい-行ってしまいました!どうも。
Simon_Weaver 2015年

11

これはとても便利なスレッドなので、このバージョンを追加したかっただけで、これは非常に単純な実装だと思います。マルチスレッドアプリケーションの場合、さまざまなタイプでこれを複数回使用しました。

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

2
これは一部のシナリオでは実行可能なソリューションであるため、反対票を投じないこと。しかし、受け入れられた答えははるかに優れています。これは技術にとらわれない(TaskSchedulerBCLの一部ですが、そうでDispatcherはありません)ので、Fire-and-Forget非同期操作(などBeginInvoke)を心配する必要がないため、複雑なタスクのチェーンを構成するために使用できます。
Kirill Shlenskiy 2014

@Kirillは少し拡張できます。WinFormsのWPFを使用する場合、一部のSOスレッドはディスパッチャを正しいメソッドとして満場一致で宣言しているためです。 GUIの更新のためだけにバックグラウンドスレッドをブロックしたくないために使用されます。FromCurrentSynchronizationContextは、ディスパッチャと同じ方法で継続タスクをメインスレッドのメッセージキューに入れませんか?
Dean

1
そうですが、OPは確かにWPFについて尋ねており(それにタグを付けています)、ディスパッチャーへの参照を保持したくありません(そして、同期コンテキストも想定しています-これはメインスレッドからしか取得できず、それへの参照をどこかに保存します)。これが私が投稿したソリューションが好きな理由です。これを必要としないスレッドセーフな静的参照が組み込まれています。これはWPFのコンテキストでは非常に役立つと思います。
Dean

3
私の最後のコメントを補強したかっただけです:開発者は同期コンテキストを保存する必要があるだけでなく、これがメインスレッドからのみ利用できることを知っている必要があります。この問題は、何十ものSO質問で混乱の原因となっています。人々は常にワーカースレッドからそれを取得しようとします。コード自体がワーカースレッドに移動された場合、この問題のために失敗します。したがって、WPFが普及しているため、この一般的な質問では、これを明確に説明する必要があります。
Dean

1
...それでも、コードがメインスレッドにない場合に同期コンテキストを追跡する必要があることについての[受け入れられた回答]に関するDeanの観察は注意することが重要であり、それを回避することはこの回答の利点です。
ToolmakerSteve 2018年

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.