21

将来のUIスレッドメッセージループの反復の後までコードの実行を延期する必要がある場合は、次のようにします。

await Task.Factory.StartNew(
    () => {
        MessageBox.Show("Hello!");
    },
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext());

これは次のようになります。await Task.Yield(); MessageBox.Show("Hello!");他にも、必要に応じてタスクをキャンセルすることもできます。

デフォルトの同期コンテキストの場合は、同様に使用できます。await Task.Runプールスレッドを続行します。

実際、私は好きですTask.Factory.StartNewそしてTask.Runより多いTask.Yieldどちらも継続コードの範囲を明示的に定義しているからです。

だから、どんな状況でawait Task.Yield()本当に便利ですか?


  • 使用したことがあるTask.Yield単体テストであいまいなASP.NETの問題を回避するにはasync方法してはいけません同期的に完了する。 - Stephen Cleary
  • 関連するTask.Yield - 本当の用途は? - noseratio
  • 私はあなたの質問がこれを尋ねなかったことを理解します、しかしそれは一種の関連しています。呼び出しMessageBox.Show()渡すことなくIWin32Window owner引数ウィンドウにフォーカスがないときにそのコードが実行されると、メッセージボックスがウィンドウの下に飛び出します。これをGUIスレッドで行うと特に混乱します。また、合格した場合IWin32WindowMessageBox.Show()あなたはUIスレッドでそうする必要があります。だから、その場合、あなたはしてはいけませんつかいますTask.Run()そしてしなければならないパスTaskSchedulerStartNew()。 - binki
  • さらに、置く必要はありませんMessageBox.Show()別のスレッドでMessageBox.Show()メッセージキューを送り出し、GUIにスケジュールされた継続を実行します。SynchronizationContext。つまり、フォームの更新と表示を続けることができます。async通常の方法でも、MessageBox.Show()見せている。 - binki

4 답변


1

ある状況Task.Yield()あなたがいるときに実際に便利ですawait再帰的に呼ばれる同期的に完了Task。 csharpのasync/await 「Zalgoをリリース」可能な場合は継続を同期的に実行することで、完全同期再帰シナリオのスタックは、プロセスが停止するほど大きくなる可能性があります。これはまた、テールコールが原因でサポートされていないことも原因の1つだと思います。Task間接。await Task.Yield()インラインではなくスケジューラによって継続が実行されるようにスケジュールします。これにより、スタックの増加を回避し、この問題を回避することができます。

また、Task.Yield()メソッドの同期部分を短くするために使用できます。呼び出し側が自分のメソッドの情報を受け取る必要がある場合Taskあなたのメソッドが何らかのアクションを実行する前に、あなたは使うことができますTask.Yield()強制的にTaskそうでなければ自然に起こるよりも早く。たとえば、次のローカルメソッドのシナリオでは、asyncメソッドはそれ自身の参照を取得することができますTask安全に(これを単一同時実行で実行していると仮定してください。SynchronizationContextwinformsやviaなどでニトロAsyncContext.Run()):

using Nito.AsyncEx;
using System;
using System.Threading.Tasks;

class Program
{
    // Use a single-threaded SynchronizationContext similar to winforms/WPF
    static void Main(string[] args) => AsyncContext.Run(() => RunAsync());

    static async Task RunAsync()
    {
        Task<Task> task = null;
        task = getOwnTaskAsync();
        var foundTask = await task;
        Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");

        async Task<Task> getOwnTaskAsync()
        {
            // Cause this method to return and let the 「task」 local be assigned.
            await Task.Yield();
            return task;
        }
    }
}

出力:

3 == 3: True

の同期部分を強制的に短くすることができるという現実のシナリオは考えられないのが残念です。async方法は何かをするための最良の方法です。先ほど示したようなトリックを実行できることを知っていると便利な場合がありますが、それはより危険にもなりがちです。多くの場合、より良い、より読みやすい、そしてよりスレッドセーフな方法でデータを渡すことができます。たとえば、ローカルメソッドにそれ自身への参照を渡すことができます。Taskを使ってTaskCompletionSource代わりに:

using System;
using System.Threading.Tasks;

class Program
{
    // Fully free-threaded! Works in more environments!
    static void Main(string[] args) => RunAsync().Wait();

    static async Task RunAsync()
    {
        var ownTaskSource = new TaskCompletionSource<Task>();
        var task = getOwnTaskAsync(ownTaskSource.Task);
        ownTaskSource.SetResult(task);
        var foundTask = await task;
        Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");

        async Task<Task> getOwnTaskAsync(
            Task<Task> ownTaskTask)
        {
            // This might be clearer.
            return await ownTaskTask;
        }
    }
}

出力:

2 == 2: True


  • 再帰への取り組みは、実際にはその良い使い方ですが、そのコストは特定の同期コンテキストに大きく依存します。 - noseratio
  • @noseratio本当です。それがオーバーヘッドを持っているという事実は、周りにカウンターを渡し、30回の再帰ごとにそれを使うだけで、少し軽減することができます。よくわかりません。SynchronizationContextしかし、それと関係があるでしょう。このようなことをしているのであれば、グラフィカルアプリケーションを使用している場合は、GUIスレッドに表示されていないことを願います。ただし、タスクAPIを繰り返し使用している場合は、待っているタスクが完了していないことが確実にわからないため、実行する必要があるかもしれません。あなたはいつでもタスクに条件付きにすることができますTask.IsCompletedそしてカウンター。 - binki

6

非同期タスクに値を返させたい場合を考えてください。

既存の同期方法

public int DoSomething()
{
    return SomeMethodThatReturnsAnInt();
}

非同期にするには、asyncキーワードを追加して戻り型を変更します。

public async Task<int> DoSomething()

Task.Factory.StartNew()を使用するには、メソッドの1行の本文を次のように変更します。

// start new task
var task = Task<int>.Factory.StartNew(
    () => {
        return SomeMethodThatReturnsAnInt();
    },
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext() );

// await task, return control to calling method
await task;

// return task result
return task.Result;

使用する場合は1行追加await Task.Yield()

// this returns control to the calling method
await Task.Yield();

// otherwise synchronous method scheduled for async execution by the 
// TaskScheduler of the calling thread
return SomeMethodThatReturnsAnInt();

後者ははるかに簡潔で読みやすく、そして実際には既存の方法をそれほど変更しません。


  • 公平に言うと、それでもまだ1行です。return await Task.Factory.StartNew(() => SomeMethodThatReturnsAnInt(), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());。それにもかかわらず、私はポイント+1を見ます。これに関する問題は、議論されている、制御フローのやや直観的でない変更です。ここに。 - noseratio
  • まあ、あなたも必要ですawait task;ライン - Moho
  • 私が向きを変える必要があるならば、これを再考SomeMethodThatReturnsAnIntasync、私は単にすることができます:public Task<int> DoSomething() { return Task.FromResult(SomeMethodThatReturnsAnInt()); }。または、async例外伝播の意味public async Task<int> DoSomething() { return await Task.FromResult(SomeMethodThatReturnsAnInt()); }。明らかにawait Task.Yield()ここでは冗長で望ましくないでしょう。投票を元に戻して申し訳ありません。 - noseratio
  • より良い単にCS1998の警告を抑制する単に間接的にコンパイラの警告を抑制するためにコンテキストスイッチを強制するよりも。 - binki

4

Task.Yield()の同期部分の「穴を開ける」には最適です。async方法。

個人的に私は私が自己相殺を持っている場合にそれが便利だと思ったasyncメソッド(独自の対応を管理するものCancellationTokenSource極めて短い期間内に(すなわち、相互依存UI要素のイベントハンドラによって)複数回呼び出される可能性がある各後続の呼び出しで前に作成されたインスタンスを取り消す。こんなときに使ってTask.Yield()続いて、IsCancellationRequestedすぐに確認してくださいCancellationTokenSourceとにかくスワップアウトされると、結果がとにかく捨てられる可能性がある高価な作業をすることを防ぐことができます。

これはSelfCancellingAsyncへの最後のキューに入れられた呼び出しだけが高価な仕事を実行して、完了まで走らせるという例です。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskYieldExample
{
    class Program
    {
        private static CancellationTokenSource CancellationTokenSource;

        static void Main(string[] args)
        {
            SelfCancellingAsync();
            SelfCancellingAsync();
            SelfCancellingAsync();

            Console.ReadLine();
        }

        private static async void SelfCancellingAsync()
        {
            Console.WriteLine("SelfCancellingAsync starting.");

            var cts = new CancellationTokenSource();
            var oldCts = Interlocked.Exchange(ref CancellationTokenSource, cts);

            if (oldCts != null)
            {
                oldCts.Cancel();
            }

            // Allow quick cancellation.
            await Task.Yield();

            if (cts.IsCancellationRequested)
            {
                return;
            }

            // Do the "meaty" work.
            Console.WriteLine("Performing intensive work.");

            var answer = await Task
                .Delay(TimeSpan.FromSeconds(1))
                .ContinueWith(_ => 42, TaskContinuationOptions.ExecuteSynchronously);

            if (cts.IsCancellationRequested)
            {
                return;
            }

            // Do something with the result.
            Console.WriteLine("SelfCancellingAsync completed. Answer: {0}.", answer);
        }
    }
}

ここでの目標は、同じコード上で同期的に実行されるコードを許可することです。SynchronizationContextasyncメソッドへの待ち合わせのない呼び出しが戻った直後(最初にヒットしたとき)await)asyncメソッドの実行に影響する状態を変更します。これは、によって達成されるのとよく似た調整です。Task.Delay(ここではゼロ以外の遅延期間について説明しています)実際の、潜在的に顕著な遅延、これは状況によっては歓迎されないことがあります。


  • あなたの考えをありがとう、私はなしで同じことをしていますがTask.YieldまたはヘルパーTask.Delay私のバージョンはこちら取り消しおよび再始動パターンの。 - noseratio
  • ところで、あなたの"肉付きの"からの例外処理には問題があるかもしれません。仕事、async voidの署名SelfCancellingAsync。 - noseratio
  • 私は特にではない簡潔にするために例外を処理する - それがパターンを使用することを選択した人の仕事です。例はvoidなぜなら、それをTaskそれは待たれるべきだと示唆していますが、これはここでは当てはまりません。例外をスローするのではなく、キャンセルが検出されたときにメソッドから戻るのもその理由です(一般的には、呼び出し側にキャンセルを通知するための推奨される方法です)。 - Kirill Shlenskiy
  • これは正しいことで、2種類の非同期メソッドの間には大きな違いがあります。いつでもasync Task例外を投げて呼び出し側にそれらを処理させることは、物事を進める上で好ましい方法です。の場合async voidただし、例外処理は通常、非同期メソッド自体の本体に組み込まれる必要があります。 - Kirill Shlenskiy
  • あなたのタスクが完全に分離されて処理されていれば大丈夫かもしれませんすべて内部の例外SelfCancellingAsync。ただし、最終的には、外部からのキャンセルを要求されるようになります(例:Mainメソッドは終了し、アプリは終了します。この時点で、あなたはおそらく保留中のタスクを待ちたいと思うでしょう。SelfCancellingAsync優雅に仕上げるために。これがあなたのアプローチでどのように行われるのかわかりません。 - noseratio

-1

Task.Yieldに代わるものではありませんTask.Factory.StartNewまたはTask.Run。彼らは全く違います。あなたがawait Task.Yield現在のスレッド上の他のコードが、スレッドをブロックせずに実行できるようにします。待っているようにそれを考えるTask.Delayを除いてTask.Yield特定の時間を待つのではなく、タスクが完了するまで待ちます。

注:使用しないでくださいTask.YieldUIスレッドで、UIが常に反応し続けると仮定します。いつもそうとは限りません。


  • 私は私がどのようによく理解していると思いますTask.Yieldそして他の習慣の待機者は働きます。この観点から、私はあなたの最後の文以外のすべてにはほとんど同意しません。 - noseratio
  • “を除くTask.Yield特定の時間を待つのではなく、タスクが完了するまで待ちます。」 - 他のタスクが完了するのを待ちません。それはただ "実行準備ができている"継続をスケジュールして、並んで待っている次の既存の "実行準備ができている"継続(それ自体かもしれない)に切り替えるだけです。すなわち、これは単にその時点であなたのタスクにそれが同期的であることをやめることを強いるだけです。 - binki

リンクされた質問


関連する質問

最近の質問