36

私は読んでいますTask.Yield、そしてJavascript開発者として、私はそれが仕事であると言うことができますまさにと同じsetTimeout(function (){...},0);メインのシングルスレッドに他のものを処理させるという意味では別名:

「全力を尽くすな。時間から解放する。他の人はそうするだろう。   いくつかもあります…」

jsでは、それは特に長いループで働いています。 (ブラウザをフリーズさせないでください。

しかし、私はこの例を見ましたここに

public static async Task < int > FindSeriesSum(int i1)
{
    int sum = 0;
    for (int i = 0; i < i1; i++)
    {
        sum += i;
        if (i % 1000 == 0) ( after a bulk , release power to main thread)
            await Task.Yield();
    }

    return sum;
}

JSプログラマーとして、私は彼らがここで何をしたのか理解することができます。

しかしC#プログラマーとして私は自分自身に尋ねる:なぜそれのためにタスクを開かないのですか?

 public static async Task < int > FindSeriesSum(int i1)
    {
         //do something....
         return await MyLongCalculationTask();
         //do something
    }

質問

Jsではタスクを開くことができません(はい私は私が実際にウェブワーカーと一緒にできることを知っています)しかし、C#で私は出来ます

もしそうなら - 私が全然それを解放することができる間に時々解放することさえ気にしないのはなぜ?

編集する

参照を追加する

からここにenter image description here

からここに(別の電子ブック):

enter image description here


  • これは悪い例です。Task.YieldUIを反応させ続けることはできません。また、asyncそしてawaitUIスレッドを解放しないでください。計算時間が長い場合は、使用する必要がありますTask.Run。 - Stephen Cleary
  • @StephenClearyタスクがスレッドプールで既に実行されており、スレッドプールが貴重なリソースであるマルチスレッドアプリケーション(ASP.NETなど)を検討する。await Task.Yield()毎秒、それから他の実行時間の短いタスクは、キューに長時間保留されずにチャンスを得るはずです。これは公平な行動ではないでしょうか。 - springy76
  • @ springy76:.NETの自動調整スレッドプールと組み合わされた最新のOSすべてに組み込まれているプリエンプティブスレッド切り替えは、マニュアルよりもはるかにうまく機能します。await Task.Yield()。 - Stephen Cleary

6 답변


49

あなたが見るとき:

await Task.Yield();

あなたはこのようにそれについて考えることができます:

await Task.Factory.StartNew( 
    () => {}, 
    CancellationToken.None, 
    TaskCreationOptions.None, 
    SynchronizationContext.Current != null?
        TaskScheduler.FromCurrentSynchronizationContext(): 
        TaskScheduler.Current);

これらすべてが、継続が将来非同期に行われることを確実にすることです。によって非同期に実行制御は、呼び出し元に戻ります。asyncメソッド、そして継続コールバックはではない同じスタックフレームで起こります。

正確にそしてどのスレッドにそれが起こるかについては完全に呼び出し側スレッドの同期コンテキストに依存します。

UIスレッドの場合継続は、メッセージループの将来の反復時に発生します。Application.RunWinFormまたは)Dispatcher.RunWPF)内部的には、それはWin32に帰着しますPostMessageカスタムメッセージをUIスレッドのメッセージキューにポストするAPI。のawaitこのメッセージが送信され処理されると、継続コールバックが呼び出されます。まさにこれがいつ起こるのかについて、あなたは完全に制御不能です。

その上、ウィンドウズはメッセージをポンピングするためにそれ自身の優先順位を持っています:情報:ウィンドウメッセージの優先順位。最も重要な部分:

このスキームでは、優先順位付けは3段階と見なすことができます。すべて   投稿されたメッセージは、ユーザーの入力メッセージよりも優先度が高くなります。   それらは異なるキューに常駐しています。そして、すべてのユーザー入力メッセージは   WM_PAINTおよびWM_TIMERメッセージより高い優先順位。

だから、あなたが使用する場合await Task.Yield()UIを反応させ続けるためにメッセージループに頼るためには、実際にはUIスレッドのメッセージループを妨害する危険があります。保留中のユーザー入力メッセージ、およびWM_PAINTそしてWM_TIMER、投稿された継続メッセージより低い優先順位を持っています。したがって、あなたがそうするならばawait Task.Yield()きついループでは、まだUIをブロックする可能性があります。

これがJavaScriptのそれとどう違うかです。setTimerあなたが質問で言及したのと同じように。 AsetTimerコールバックが呼び出されます後にすべてのユーザ入力メッセージはブラウザのメッセージポンプによって処理されています。

そう、await Task.Yield()UIスレッドでバックグラウンド作業をするのには向いていません。実際、UIスレッドでバックグラウンドプロセスを実行する必要はほとんどありませんが、実行することもあります。この場合は、フレームワークのアイドル状態のインフラストラクチャを使用してください。

例えば、WPFを使えばあなたはできるawait Dispatcher.Yield(DispatcherPriority.ApplicationIdle)

async Task DoUIThreadWorkAsync(CancellationToken token)
{
    var i = 0;

    while (true)
    {
        token.ThrowIfCancellationRequested();

        await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);

        // do the UI-related work item
        this.TextBlock.Text = "iteration " + i++;
    }
}

WinFormsでは、あなたは使用することができますApplication.Idleイベント:

// await IdleYield();

public static Task IdleYield()
{
    var idleTcs = new TaskCompletionSource<bool>();
    // subscribe to Application.Idle
    EventHandler handler = null;
    handler = (s, e) =>
    {
        Application.Idle -= handler;
        idleTcs.SetResult(true);
    };
    Application.Idle += handler;
    return idleTcs.Task;
}

それをお勧めしますUIスレッドで実行されているこのようなバックグラウンド操作の繰り返しごとに50ミリ秒を超えないようにしてください。

非UIスレッドの場合同期コンテキストなしawait Task.Yield()継続をランダムプールスレッドに切り替えるだけです。それがになるという保証はありません。違う現在のスレッドからのスレッド、それは非同期継続。もしThreadPool飢えている、それは同じスレッドへの継続をスケジュールすることがあります。

ASP.NETでしているawait Task.Yield()で説明されている回避策を除いて、まったく意味がありません。@ StephenClearyの答え。それ以外の場合は、冗長なスレッド切り替えを使用した場合にのみWebアプリケーションのパフォーマンスが低下します。

だから、ですawait Task.Yield()有用?IMO、あまりありません。次のようにして継続を実行するためのショートカットとして使用できます。SynchronizationContext.PostまたはThreadPool.QueueUserWorkItemあなたが本当にあなたのメソッドの一部に非同期を課す必要があるならば。

あなたが引用した本に関して、私の意見では、使用するためのこれらのアプローチTask.Yield間違っている。私は上で、なぜそれらがUIスレッドにとって間違っているのかを説明しました。非UIプールスレッドの場合、単純にはありません。「実行するスレッド内の他のタスク」あなたがのようなカスタムタスクポンプを実行していない限り、スティーブントゥーブAsyncPump

コメントに答えるために更新されました:

...どうやって非同期操作と同じスレッドに留まることができるのか   ?

簡単な例として:WinFormsアプリ:

async void Form_Load(object s, object e) 
{ 
    await Task.Yield(); 
    MessageBox.Show("Async message!");
}

Form_Load呼び出し元(起動したWinFromsフレームワークコード)に戻ります。Loadこれにより、メッセージボックスが実行されるメッセージループの将来の反復で、メッセージボックスが非同期に表示さApplication.Run()。継続コールバックは、キューに入れられます。WinFormsSynchronizationContext.Postこれは、プライベートWindowsメッセージをUIスレッドのメッセージループに内部的に投稿します。このメッセージが送信されたとき、まだ同じスレッド上でコールバックが実行されます。

コンソールアプリケーションでは、次のようにして同様のシリアル化ループを実行できます。AsyncPump上記の通り。


  • 気をつけて、こんにちは:-)。あなたは言った:「現在のスレッドとは異なるスレッドになるという保証はありません。非同期の継続であることが保証されているだけです」。 - しかし、それが同じスレッドに留まる場合 - 操作は同期操作になります。いいえ?言い換えれば、どのように非同期操作と同じスレッドに留まることができますか? asynchrounseyは継続についてのものです。しかし、それが同じスレッド内にある場合、それはオプションを離れなかった可能性がより高いです.....いいえ - Royi Namir
  • @ RoyiNamirさん、更新を確認して、同じスレッドで非同期的に続行することは可能です。 - noseratio
  • はい:)、しかしあなたはコメントをに書いた。非UIスレッド私は混乱しています..... - Royi Namir
  • @RoyiNamir、あなたはそれを魔法のように、UI以外のスレッドでも行うことができます。SynchronizationContext。見ましたかAsyncPump?結局のところ、非同期はマルチスレッドを想定していません。それは、将来的に成果をもたらすものにすぎません。あなたが対処するときでも、注意してくださいThreadPools.contextがないと、継続が同じスレッドで発生する可能性があります(たとえば、同じプールスレッドが開始し、後で非同期I / O操作の完了に役立つことがあります)。 - noseratio
  • @ bN_、あります:protected virtual Task OverridableMethod() { return Task.Task.CompletedTask; }。注意asyncこれは仮想メソッドシグネチャの一部ではないため、派生クラスでこのメソッドをオーバーライドするときにも使用できます。 - noseratio

16

見つけただけTask.Yield2つのシナリオで役立ちます。

  1. 単体テスト。テスト対象のコードが非同期の場合に適切に機能することを確認する
  2. 識別コードが不明瞭なASP.NETの問題を回避するにはできない同期的に完了する


6

いいえ、使用するのとは異なりますsetTimeoutUIに制御を戻します。 Javascriptでは、常にUIを次のように更新します。setTimeout常に数ミリ秒以上の一時停止があり、保留中のUI作業はタイマーより優先されますawait Task.Yield();そうではありません。

利回りがメインスレッドで実行されることを保証するものではありません。反対に、利回りを呼び出すコードがUI作業より優先されることがよくあります。

「ほとんどのUIのUIスレッドに存在する同期コンテキスト   多くの場合、環境はコンテキストに投稿された作業を優先します   入力やレンダリング作業よりも。このため、待つことに頼らないでください。   Task.Yield(); UIを即応させるため」

参照:MSDN:Task.Yieldメソッド


  • それに頼らないならば - それでそれがそこにあるもののために?あなたは本当の例を供給することができますか? - Royi Namir
  • @RoyiNamir:マルチスレッドを処理するためのものですが、UIの応答性を維持するために信頼できる方法ではありません。メソッドを非同期にしますが、CPUのメインスレッドと競合しないようにします。 - Guffa
  • もしそれがメインスレッドの優先順位を勝ち取るかもしれないなら、なぜ私はそれを使いたいのだろうか?あなたが言っているのは、コードが非同期ではなく同期を実行する可能性があるということです。 - Royi Namir
  • @ RoyiNamir:コードは非同期的に実行されますが、作業の優先順位付けの方法により、コードは非同期的に実行されるという印象を受けるかもしれません。非同期タスクは何かが起こるのを待っている仕事にもっと適しています。AsParallel方法。 - Guffa

2

まず最初に明確にさせてください。Yieldとまったく同じではありませんsetTimeout(function (){...},0);。 JSはシングルスレッド環境で実行されるので、それが他のアクティビティを実行させる唯一の方法です。やや協調マルチタスク。 .netは、明示的なマルチスレッドを使用したプリエンプティブマルチタスク環境で実行されます。

今に戻るThread.Yield。私が言ったように、.netは先制の世界に住んでいますが、それはそれより少し複雑です。 C#await/asyncステートマシンによって支配されるそれらのマルチタスクモードの面白い混合を作成しなさい。省略した場合Yieldあなたのコードからそれはただスレッドをブロックするでしょう、そしてそれはそれです。通常のタスクにしてstart(またはスレッド)を呼び出すだけで、それが並列に行われ、後でtask.Resultが呼び出されたときにスレッドの呼び出しをブロックします。あなたがするとどうなりますかawait Task.Yield();もっと複雑です。論理的には呼び出し側のコードのブロックを解除し(JSと同様)、実行が続行されます。それが実際に何をしているか - それは別のスレッドを選択し、呼び出しスレッドを使ってプリエンプティブ環境でその中で実行を継続します。それで最初までスレッドを呼び出していますTask.Yieldそしてそれはそれ自身の上にあります。その後の呼び出しTask.Yieldどうやら何もしないようです。

簡単なデモ:

class MainClass
{
    //Just to reduce amont of log itmes
    static HashSet<Tuple<string, int>> cache = new HashSet<Tuple<string, int>>();
    public static void LogThread(string msg, bool clear=false) {
        if (clear)
            cache.Clear ();
        var val = Tuple.Create(msg, Thread.CurrentThread.ManagedThreadId);
        if (cache.Add (val))
            Console.WriteLine ("{0}\t:{1}", val.Item1, val.Item2);
    }

    public static async Task<int> FindSeriesSum(int i1)
    {
        LogThread ("Task enter");
        int sum = 0;
        for (int i = 0; i < i1; i++)
        {
            sum += i;
            if (i % 1000 == 0) {
                LogThread ("Before yield");
                await Task.Yield ();
                LogThread ("After yield");
            }
        }
        LogThread ("Task done");
        return sum;
    }

    public static void Main (string[] args)
    {
        LogThread ("Before task");
        var task = FindSeriesSum(1000000);
        LogThread ("While task", true);
        Console.WriteLine ("Sum = {0}", task.Result);
        LogThread ("After task");
    }
}

結果は次のとおりです。

Before task     :1
Task enter      :1
Before yield    :1
After yield     :5
Before yield    :5
While task      :1
Before yield    :5
After yield     :5
Task done       :5
Sum = 1783293664
After task      :1
  • Mac OS X上のモノ4.5で生成された出力、他の設定では結果が異なる場合があります

動いたらTask.Yieldメソッドの上にそれは最初から非同期になり、呼び出し元のスレッドをブロックしません。

結論:Task.Yield同期コードと非同期コードを混在させることができます。多かれ少なかれ現実的なシナリオ:あなたはいくらかの重い計算操作とローカルキャッシュとタスクを持っていますCalcThing。このメソッドでは、アイテムがキャッシュにあるかどうかをチェックし、そうであれば - 存在しない場合はアイテムを返します。Yieldそしてそれを計算してください。実際にあなたの本からのサンプルはそこでは何も役に立つことが達成されていないのでかなり無意味です。 GUIのインタラクティビティに関する彼らの発言は、単なるひどく不適切なものです(UIスレッドは、最初の呼び出しまでロックされます)。Yield、あなたはそれをするべきではありません、MSDNその上で明確(そして正しい)です: "待たされたTask.Yield()に頼らないでください; UIを敏感に保つために"


  • JSとの類似点は、c#のGUIアプリケーションにUIを実行する単一のメインスレッドがあるという点です。JSのシングルループと同じです。 (そしてJSでは - 解決策はsetTimeout(...,0 ) - Royi Namir
  • デモが大好きです:) - Noctis

0

あなたは、長期実行関数がバックグラウンドスレッドで実行できるものであると仮定しています。そうでなければ、例えば、それがUIインタラクションを持っているので、それから実行中にUIをブロックするのを防ぐ方法がないので、実行時間はユーザに問題を引き起こさないように十分に短く保たれるべきです。

もう1つの可能性は、バックグラウンドスレッドよりも長期実行関数があることです。そのようなシナリオでは、これらの関数のいくつかがすべてのスレッドを占有しないようにすることをお勧めします。


  • 最初のケースは潜在的に有効ですが、2番目のケースは実際には有効ではありません。スレッドよりも多くの作業、または作業者がいる場合は、単にその時間を共有する必要があります。その作業の一部をUIスレッドに提供しても意味がありません。しなかった場合、それは単にUIスレッドがアイドリングしていることを意味し、その結果ワーカースレッドにその時間を与えます。 - Servy
  • と言ってUIインタラクション - あなたは現れることができるコードについて話しています(前||後)await。右 ? - Royi Namir
  • あなたが持っている必要がありますたくさんのそれが起こるために、あなたがスレッドを持っているより多くの操作。それはスレッド飢餓と呼ばれます。最も極端な場合を除いて、スレッドのスケジューラは各スレッドの実行を変更することで問題を解決することができ、各操作を完了するまで実行するのではなく、短い時間を実行することができます。の上にそれスレッドプールを使用することで問題をはるかに効果的に解決できます。スレッドプールは、任意の数のワーカーを配置でき、スループットを最適化するように実際の作業をキューに入れることができます。 - Servy
  • @hvdいいえ、これは問題の原因となるものではありません。スレッドのスケジューラはそれほど貧弱ではなく、遅れを知覚するためにUIスレッドを使い果たす必要がある時間の長さは、かなり高いです。他のスレッドは、CPU時間を永遠に保持することはできません。事実、彼らはその問題をほとんど管理できません。 UIスレッドの管理とは異なり、協調的なシステムではありませんUIスレッド内から誰かが欲しい限り、誰かが意図的にUIスレッドを独占的に独占することができます。 - Servy
  • @hvd技術的には、これは私が述べた2番目のオプションを達成するための1つの方法に過ぎません。それは、それを達成するための設計が非常に良くない方法です。タスクが分割されるのに適している場合は、分割します。そうでない場合は、貼り付けるだけですLongRunningそれよりもむしろyieldあらゆる所に。 - Servy

0

Task.Yieldを使用する場合、誰も本当の答えを提供しなかったと思います。 タスクが終わりのないループ(または長時間の同期ジョブ)を使用し、スレッドプールスレッドを排他的に保持できる(他のタスクがこのスレッドを使用することを許可しない)場合は、主に必要です。これは、ループ内でコードが同期的に実行されると発生する可能性があります。のTask.Yieldのスケジュール変更スレッドプールキューへのタスクおよびスレッドを待っていた他のタスクを実行できます。

例:

  CancellationTokenSource cts;
  void Start()
  {
        cts = new CancellationTokenSource();

        // run async operation
        var task = Task.Run(() => SomeWork(cts.Token), cts.Token);
        // wait for completion
        // after the completion handle the result/ cancellation/ errors
    }

    async Task<int> SomeWork(CancellationToken cancellationToken)
    {
        int result = 0;

        bool loopAgain = true;
        while (loopAgain)
        {
            // do something ... means a substantial work or a micro batch here - not processing a single byte

            loopAgain = /* check for loop end && */  cancellationToken.IsCancellationRequested;
            if (loopAgain) {
                // reschedule  the task to the threadpool and free this thread for other waiting tasks
                await Task.Yield();
            }
        }
        cancellationToken.ThrowIfCancellationRequested();
        return result;
    }

    void Cancel()
    {
        // request cancelation
        cts.Cancel();
    }


  • 2番目の考えからは、おそらくそのように使用することができますが、良い設計のようには思えません。CPU範囲のタスクをスレッドプールにキューイングする(処理できる以上のもの)と譲るTask.Yield()彼らの仲間に実行する機会を与えるため。このような同時CPUバウンドシナリオには、より良い選択肢があります。ParallelまたはTPLデータフロー。 - noseratio
  • @noseratioループ内のコードがユーザー定義のハンドラを実行する場合に、このパターンが使用される可能性があります。コードは、それがどのように振る舞うのかを知らない - それが非同期であるか同期であるか。念のため、ループの最後にはTask.Yieldを用意してください。これはメッセージ処理バスでは一般的です。バスがハンドラをインスタンス化し、メッセージを処理するとき。並列実行するワーカーが多数いる場合は、スレッドプールのスレッドを厳密にすることができます。私はそれを使用してワーカーコーディネーターを実装しましたgithub.com/BBGONE/REBUS-TaskCoordinator - Maxim T
  • そしてもう一つ - これはテストすることができます。私はTaskCoordinatorテストラボを持っていますgithub.com/BBGONE/TaskCoordinatorこれはこのパターンを使用します。メッセージを処理するために多数のタスクを並行して実行するように設定した場合、Task.Yieldを使用しないと、コンソールにメッセージが表示されません - すべてのプールスレッドがビジーで、threadpoolがそれ以上追加しないためです。高いCPU使用率の。ただし、Task.Yieldを使用すると、ステータスメッセージがコンソールに表示されます。そして、それは毎秒約200 000のメッセージを処理し、そしてより少ないオーバーヘッドがあったとき(シリアル化なし) - 毎秒約500 000。 - Maxim T
  • プロデューサ/コンシューマパターンを実装しながらThreadPoolの飢餓を克服するためにTask.Yieldを使用することはお勧めできません。理由を詳しく調べたい場合は、別の質問をすることをお勧めします。 - noseratio
  • @noseratioはあなたのように聞こえます:)stackoverflow.com/questions/53263258/… - Marc Gravell

リンクされた質問


関連する質問

最近の質問