web-dev-qa-db-ja.com

Task.Yield-実際の使用?

私は_Task.Yield_について読んでいます 正確に メインのシングルスレッドに他の別名を処理させるという点では、setTimeout(function (){...},0);と同じです。

「すべての力を奪うのではなく、時間から解放してください-他の人もそうするでしょう...」

Jsでは、特に長いループで機能しています。 (ブラウザをフリーズさせない...

しかし、この例を見ました here

_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#ではできます

If So-なぜそれをリリースできるのに、なぜリリースするのが面倒なのでしょうか?

編集

参照の追加:

ここ から: enter image description here

here (別の電子書籍)から:

enter image description here

38
Royi Namir

あなたが見るとき:

await Task.Yield();

次のように考えることができます:

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

これにより、将来的に継続が非同期的に行われるようになります。 asynchronouslyでは、実行制御はasyncメソッドの呼び出し元に戻り、継続コールバックは同じスタックフレームでnotになります。

正確にどのスレッドで発生するかは、呼び出し元のスレッドの同期コンテキストに完全に依存します。

UIスレッドの場合、継続はApplication.Runによって実行されるメッセージループの将来の反復で発生します。 WinForms )またはDispatcher.Run[〜#〜] wpf [〜#〜] )。内部的には、Win32 PostMessage AP​​Iにより、UIスレッドのメッセージキューにカスタムメッセージが投稿されます。このメッセージがポンピングおよび処理されると、await継続コールバックが呼び出されます。まさにこれがいつ起こるかについて、あなたは完全にコントロールできません。

さらに、Windowsには、メッセージをポンピングするための独自の優先順位があります: INFO:Window Message Priorities 。最も重要な部分:

このスキームでは、優先順位付けは3レベルと見なすことができます。投稿されたすべてのメッセージは、異なるキューに存在するため、ユーザー入力メッセージよりも優先されます。また、すべてのユーザー入力メッセージは、WM_PaintおよびWM_TIMERメッセージよりも優先されます。

そのため、await Task.Yield()を使用してUIの応答性を維持しようとしてメッセージループに屈すると、実際にはUIスレッドのメッセージループを妨害する危険があります。いくつかの保留中のユーザー入力メッセージ、およびWM_PaintおよびWM_TIMERは、投稿された継続メッセージよりも優先度が低くなります。したがって、タイトループでawait Task.Yield()を実行すると、UIがブロックされる可能性があります。

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

そのため、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()は単に継続をランダムプールスレッドに切り替えます。現在のスレッドとdifferentスレッドになる保証はありません。asynchronous継続であることが保証されるだけです。 ThreadPoolが飢えている場合、同じスレッドへの継続をスケジュールできます。

ASP.NETでは、await Task.Yield()を実行しても、 に記載されている回避策を除いて、まったく意味がありません。 @StephenClearyの答え 。それ以外の場合は、冗長スレッドスイッチを使用するだけでWebアプリのパフォーマンスが低下します。

それで、await Task.Yield()は便利ですか?IMO、それほど多くはありません。メソッドの一部に非同期を強制する必要がある場合、SynchronizationContext.PostまたはThreadPool.QueueUserWorkItemを介して継続を実行するショートカットとして使用できます。

引用した本について、私の意見では、Task.Yieldを使用するアプローチは間違っています。上記のUIスレッドで間違っている理由を説明しました。非UIプールスレッドの場合、 Stephen ToubのAsyncPumpなどのカスタムタスクポンプを実行しない限り、単に"実行するスレッド内の他のタスク"はありません。

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

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

簡単な例:WinFormsアプリ:

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

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

コンソールアプリでは、上記のAsyncPumpを使用して同様のシリアル化ループを実行できます。

59
noseratio

私が見つけたのはTask.Yield 2つのシナリオで役立ちます。

  1. 非同期の存在下でテスト対象のコードが適切に機能することを確認するための単体テスト。
  2. IDコードcannotが同期的に完了する不明なASP.NETの問題を回避するには
17
Stephen Cleary

いいえ、setTimeoutを使用してUIに制御を返すのとまったく同じではありません。 Javascriptでは、setTimeoutは常に数ミリ秒の最小休止時間を持ち、保留中のUI作業がタイマーよりも優先されるため、UIを常に更新できますが、await Task.Yield();はそうしません。

Yieldがメインスレッドで何らかの作業を実行できるという保証はありません。逆に、yieldを呼び出したコードはUIの作業よりも優先されることがよくあります。

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

参照: MSDN:Task.Yield Method

6
Guffa

まず最初に、YieldsetTimeout(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の応答性を維持する」。

2
Andrey

長時間実行される関数は、バックグラウンドスレッドで実行できる関数であると仮定しています。そうでない場合、たとえば、UIの対話があるため、実行中にUIがブロックされるのを防ぐ方法はないため、ユーザーに問題を引き起こさないように、実行時間を短くする必要があります。

もう1つの可能性は、バックグラウンドスレッドよりも実行時間が長い関数があることです。そのシナリオでは、これらの関数のいくつかがすべてのスレッドを占有しないようにする方が良いかもしれません(または、問題ではないかもしれません)。

0
user743382

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

例:

  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();
    }
0
Maxim T