2016-05-30 11 views
2

既にこのコンテキストに投稿されているTaskを作成するにはどうすればいいですか(これは基本的には特定のスレッドのウィンドウです)SynchronizationContextがあるとします。特定のSynchronizationContextに投稿されるタスクを作成するにはどうすればよいですか?

参考のために、ここにはSynchronizationContextの設定方法のデモンストレーション非常に基本的なのデモンストレーションがあります。

public class SomeDispatcher : SynchronizationContext 
{ 
    SomeDispatcher() { 

     new Thread(() => { 

      SynchronizationContext.SetSynchronizationContext(this); 

      // Dispatching loop (among other things) 

     }).Start(); 
    } 

    override void Post(SendOrPostCallback d, object state) 
    { 
     // Add (d, state) to a dispatch queue; 
    } 
} 

非同期/がすでにコンテキストで実行されているを待っためにこれが正常に動作します。

ここでは、外部のコンテキスト(UIスレッドなど)からTaskをこれに投稿できるようにしたいが、これを行うためのきれいな方法を見つけることはできないようだ。

これを行う1つの方法は、TaskCompletionSource<>を使用することです。

Task StartTask(Action action) 
{ 
    var tcs = new TaskCompletionSource<object>(); 
    SaidDispatcher.Post(state => { 
     try 
     { 
      action.Invoke(); 
      tcs.SetResult(null); 
     } 
     catch (Exception ex) 
     { 
      tcs.SetException(ex); 
     } 
    }); 
    return tcs.Task; 
}); 

しかし、これはSynchronizationContextからTaskFactoryインタフェースは、理想的には、おそらく

などホイールとバリエーションなどStartNew(Func<TResult>)StartNew(Func<Task<TResult>>)を支える主要な痛みを、再発明されていますが、私は1つのインスタンスを作成するように見えることはできませんきれい:

TaskFactory CreateTaskFactory() 
{ 
    var original = SynchronizationContext.Current; 
    SynchronizationContext.SetSynchronizationContext(SomeDispatcher); // yuck! 
    try 
    { 
     return new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()); 
    } 
    finally 
    { 
     SynchronizationContext.SetSynchronizationContext(original); 
    } 
} 

(。つまり、現在のスレッドの同期コンテキストはハックと思われる一時的なホースを有する)

+0

既存のSynchronizationContextTaskSchedulerのロジックをコピーしてカスタムTaskSchedulerを実装するとどうなりますか?コピーは必須ではありませんが、ロジック自体は非常に単純です。 – Evk

+0

@エヴァークあなたが使うことができない内部的なものがいくつかありますが、主なアイデアはまだあります。あなた自身の 'TaskScheduler'が必要です。 – Luaan

+1

タスクを同期コンテキストに投稿する理由を教えてください。同期コンテキストの通常の使用は、同期コンテキストで開始されます。 –

答えて

4

これはデフォルトSynchronizationContextTaskSchedulerのみ現在の同期コンテキスト

で動作します。しかし、それはソースコードだ

  • 内部

    1. あるらしい提供されていますhere、我々は、それは比較的簡単ですので、我々はロールしようとすることができます参照してください

      public sealed class MySynchronizationContextTaskScheduler : TaskScheduler { 
          private readonly SynchronizationContext _synchronizationContext; 
      
          public MySynchronizationContextTaskScheduler(SynchronizationContext context) { 
           _synchronizationContext = context; 
          } 
      
          [SecurityCritical] 
          protected override void QueueTask(Task task) { 
           _synchronizationContext.Post(PostCallback, task); 
          } 
      
          [SecurityCritical] 
          protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { 
           if (SynchronizationContext.Current == _synchronizationContext) { 
            return TryExecuteTask(task); 
           } 
           else 
            return false; 
          } 
      
          [SecurityCritical] 
          protected override IEnumerable<Task> GetScheduledTasks() { 
           return null; 
          } 
      
          public override Int32 MaximumConcurrencyLevel 
          { 
           get { return 1; } 
          } 
      
          private void PostCallback(object obj) { 
           Task task = (Task) obj; 
           base.TryExecuteTask(task); 
          } 
      } 
      

      次に、あなたのCreateTaskFactoryは次のようになります。

      TaskFactory CreateTaskFactory() { 
          return new TaskFactory(new MySynchronizationContextTaskScheduler(SomeDispatcher)); 
      } 
      

      そして、あなたがしてタスクを作成します。正確に何をしたいん

      var factory = CreateTaskFactory(); 
      var task = factory.StartNew(...); 
      
  • +0

    タスクスケジューラを書く方法ではなく、タスクを追加する方法が問題だと考えて、これを使用する方法の例を追加することもできますか? – Default

    +0

    @Defaultが追加されました。 – Evk

    +0

    これは素晴らしいですね!なぜ彼らは 'TaskScheduler.FromSynchronizationContext(SynchronizationContext)'を提供していないのだろうと思っています。古い同期コンテキストを許可することにいくつかの設計上の問題があるからですが、私は考えることはできません。 (編集:削除されたノイズ) – antak

    2

    Parallel Extensions Extras contains SynchronizationContextTaskScheduler

    自分でPEEをコンパイルしたくない場合はthere is an unofficial NuGet package for itです。

    通常、これを行う必要はないはずですが、これを求めているという事実はデザインの欠陥を示している可能性があります。

    +0

    このユースケースを知ることは、少なくとも間接的にサポートされています。 *私は高度にカスタマイズされたタスクディスパッチャー(そのコアにループを呼び出すタスクを持つ単一のスレッド)を持っているとします(遡及的に)* async */* await *を追加したい場合は、サポート。自然な進行は、 'SynchronizationContext' - >' TaskScheduler' - > 'TaskFactory'を実装しているようです。より良い方法がありますか? (または、あなたは可能性のある問題かもしれないカスタムディスパッチャを作成する必要があると言っていましたか?) – antak

    +0

    @antakなぜあなたは 'SynchronizationContext'を必要としますか?カスタムの「TaskScheduler」で十分ではないでしょうか? – svick

    +0

    私は、 'syncctx.Current'の代わりに' TaskScheduler.Current'を[get * await * sを同じスレッドで再開する]に設定できると思ったので、しばらく興奮しました(https://msdn.microsoft.com/ magazine/gg598924.aspx)。しかし、 'TaskScheduler.Current'は(私は全く異なるものと思われます)(http://stackoverflow.com/a/23072503/1036728)、私が設定できるスレッドローカルではありません。もしディスパッチャーがすべて 'TaskScheduler'を通って来たら、これはおそらく問題ではありませんが、' Task'の外でルーチンを呼び出すと、* await *が壊れ始めます。私は行方不明のものがありますか? – antak

    関連する問題