16

今日、SwitchTo メソッドを使用して GUI スレッドに切り替えようとしましたが、メソッドがそこにないという理由だけで、持ち上げた例が機能しないことがわかりました。

次に、この宣伝文句をここで見つけました:

私たちがそれを取り除いた理由は、それが非常に危険だったからです。別の方法は、TaskEx.Run... 内にコードをまとめることです。

私の質問は単純です:なぜ危険だったのですか? それを使用すると、具体的にどのような危険が生じるのでしょうか?

私はその投稿の残りの部分を読んだことに注意してください。したがって、ここに技術的な制限があることを理解しています. 私の質問は、私がこれを認識している場合、なぜ危険なのですか?

指定された機能を提供するためにヘルパー メソッドを再実装することを検討していますが、誰かが危険だと判断した以外に根本的に壊れているものがある場合は、実装しません。

具体的には、非常に単純ですが、必要なメソッドの実装を検討する方法は次のとおりです。

public static class ContextSwitcher
{
    public static ThreadPoolContextSwitcher SwitchToThreadPool()
    {
        return new ThreadPoolContextSwitcher();
    }

    public static SynchronizationContextSwitcher SwitchTo(this SynchronizationContext synchronizationContext)
    {
        return new SynchronizationContextSwitcher(synchronizationContext);
    }
}

public class SynchronizationContextSwitcher : INotifyCompletion
{
    private readonly SynchronizationContext _SynchronizationContext;

    public SynchronizationContextSwitcher(SynchronizationContext synchronizationContext)
    {
        _SynchronizationContext = synchronizationContext;
    }

    public SynchronizationContextSwitcher GetAwaiter()
    {
        return this;
    }

    public bool IsCompleted
    {
        get
        {
            return false;
        }
    }

    public void OnCompleted(Action action)
    {
        _SynchronizationContext.Post(_ => action(), null);
    }

    public void GetResult()
    {
    }
}

public class ThreadPoolContextSwitcher : INotifyCompletion
{
    public ThreadPoolContextSwitcher GetAwaiter()
    {
        return this;
    }

    public bool IsCompleted
    {
        get
        {
            return false;
        }
    }

    public void OnCompleted(Action action)
    {
        ThreadPool.QueueUserWorkItem(_ => action(), null);
    }

    public void GetResult()
    {
    }
}

これにより、次のようなコードを書くことができます。

public async void Test()
{
    await ContextSwitcher.SwitchToThreadPool(); // ensure we're not bogging down the UI thread
    // do some heavy processing
    await _UIContext.SwitchTo(); // presumably saved from the main thread
    // update UI with new data
}
4

4 に答える 4

9

Stephen Toub は、このスレッドで推論に関する詳細情報を提供しています。

要約すると、次の 2 つの理由から、これはお勧めできません。

  1. 非構造化コードを促進します。実行する必要がある「重い処理」がある場合は、Task.Run. さらに良いことに、ビジネス ロジックを UI ロジックから分離します。
  2. エラー処理と (一部の) 継続は、不明なコンテキストで実行されます。catch/finally内のブロックは、スレッド プールまたはUI コンテキストTestでの実行を処理する必要があります (スレッド プール コンテキストで実行されている場合、UI コンテキストでジャンプするために使用することはできません)。また、返された場合は問題ありません (必要に応じて継続コンテキストを修正します) が、を使用する明示的な継続がある場合、 /ブロックと同じ問題が発生します。SwitchToawaitTaskawaitContinueWithExecuteSynchronouslycatchfinally

要するに、コードはよりクリーンで、より予測可能SwitchToです。

于 2013-03-12T14:56:58.443 に答える
5

ConfigureAwait は実際には SwitchTo よりも危険です。現在のコンテキストと最後の SwitchTo 呼び出しを頭の中で追跡することは、変数が最後に割り当てられた場所を追跡することと同じくらい難しくありません。一方、ConfigureAwait は、呼び出しが実際に非同期で実行された場合にのみ、コンテキストを切り替えます。タスクがすでに完了している場合、コンテキストは保持されます。これを制御することはできません。

于 2014-02-06T22:38:54.260 に答える
2

この GitHub issueのDavid Fowler と Stephen Toub によると、 2020 年になり、すぐに CLR に戻るようにSwitchTo設定されているようです。awaittrycatch

IMO、特にコードがカスタム同期コンテキストで実行されないようにしたい場合は、サードパーティのライブラリコードにawait TaskScheduler.Default.SwitchTo()依存するよりも明示的に使用する方が良い. の実験的な実装を含む、それに関する詳細をConfigureAwait(false)ブログに投稿しています。SwitchTo

一言で言えば、以下の最初のオプションは、最小限の定型コードで意図を明確に示していると思います。

// switch to the thread pool explicitly for the rest of the async method
await TaskScheduler.Default.SwitchTo();
await RunOneWorkflowAsync();
await RunAnotherWorkflowAsync();
// execute RunOneWorkflowAsync on the thread pool 
// and stay there for the rest of the async method
await Task.Run(RunOneWorkflowAsync).ConfigureAwait(false);
await RunAnotherWorkflowAsync();
await Task.Run(async () => 
{
  // start on the thread pool
  await RunOneWorkflowAsync();
  await RunAnotherWorkflowAsync();
}).ConfigureAwait(false);
// continue on the thread pool for the rest of the async method
// start on whatever the current synchronization context is
await RunOneWorkflowAsync().ConfigureAwait(false);
// continue on the thread pool for the rest of the async method,
// unless everything inside `RunOneWorkflowAsync` has completed synchronously
await RunAnotherWorkflowAsync();
于 2020-09-28T08:23:05.983 に答える