2013-03-15 20 views
32

Task<T>に返信する方法があります。awaitを自由に使えます。私はそれらのタスクをデフォルトのものの代わりにカスタムTaskSchedulerで実行させたいと思っています。awaitを使用してカスタムTaskSchedulerでタスクを実行するにはどうすればよいですか?

var task = GetTaskAsync(); 
await task; 

私は新しいTaskFactory (new CustomScheduler())を作成し、それからStartNew()を行うことができます知っているが、StartNew()行動をとり、Taskを作成し、私はすでに(TaskCompletionSourceで舞台裏で返される)Task

を持っています

awaitのために自分自身のTaskSchedulerを指定するにはどうすればよいですか?

+1

ホット(別名既に実行中)タスクを待っていませんか? TaskCompletionSourceのタスクは、SetResultを呼び出してタスクを完了させる責任を負うため、実行することもできません。待機からの継続が実行されるスケジューラを指定する方法を意味しますか? – sanosdole

+0

タスクはホットで実行中で、CTSから返されます。しかし、いくつかのスケジューリングは 'SetResult'と待った後の継続との間で起こります。可能であれば、私自身のスケジューラでスケジューリングを制御する必要があります –

+0

@StephaneDelcroixあなたのスケジューラでメソッド全体を実行できませんか?私はそれが最善の解決策だと思う。 – svick

答えて

1

コメントの後、awaitが実行された後のコードのスケジューラを制御したいようです。

コンパイルは、デフォルトで現在のSynchronizationContextで実行される待機から継続を作成します。ですから、あなたのベストショットは、awaitを呼び出す前にSynchronizationContextを設定することです。

特定のコンテキストを待つ方法がいくつかあります。このような実装方法の詳細については、Jon SkeetのConfigure Await、特にSwitchToの部分を参照してください。

EDIT: TaskExのSwitchToメソッドが誤用されすぎて削除されました。理由はMSDN Forumを参照してください。

33

私は実際にはTask.Runをやってみたいと思っていますが、カスタムスケジューラを使っています。 StartNewは非同期メソッドでは直感的に動作しません。 Stephen Toubはthe differences between Task.Run and TaskFactory.StartNewについて素晴らしいブログ投稿をしています。

private static readonly TaskFactory myTaskFactory = new TaskFactory(
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, 
    TaskContinuationOptions.None, new MyTaskScheduler()); 
private static Task RunOnMyScheduler(Func<Task> func) 
{ 
    return myTaskFactory.StartNew(func).Unwrap(); 
} 
private static Task<T> RunOnMyScheduler<T>(Func<Task<T>> func) 
{ 
    return myTaskFactory.StartNew(func).Unwrap(); 
} 
private static Task RunOnMyScheduler(Action func) 
{ 
    return myTaskFactory.StartNew(func); 
} 
private static Task<T> RunOnMyScheduler<T>(Func<T> func) 
{ 
    return myTaskFactory.StartNew(func); 
} 

その後、カスタムスケジューラの同期または非同期メソッドを実行することができます。

ので、独自のカスタムRunを作成するには、このような何かを行うことができます。

TaskCompletionSource<T>.Task
+0

本当に仕事をしている仕事はデフォルトの 'ThreadPoolTask​​Scheduler'でスケジュールされています - あなたの' MyTaskScheduler'は内部タスクをスケジュールするために使用されています。 – springy76

+1

@ springy76:いいえ、それは間違っています。 'MyTaskScheduler'は' func'を実行し、非同期 'func'の場合、' await'はデフォルトで 'MyTaskScheduler'で再開します。 –

+0

私は 'LimitedConcurrencyLevelTask​​Scheduler'の例をとり、それをLIFOに変更しました。しかし、すべてのタスクはFIFO順に実行され、制限はありませんが、 'TaskScheduler.Current'は、任意のデバッグポイントでカスタムスケジューラを返します。スケジューラは、 'Parallel.ForEach'で使用されているとき、または' myTaskFactory.StartNew() 'を使って実際の' Func 'を実行するときに期待どおりに動作しますが、' Func > 'と' Unwrap()最初の 'await'の前に、スケジューラーが完全に無視されていると言っても、正しい順序でも限定された並行性でもコードを実行しません。 – springy76

7

は、任意のアクションなしで構成され、スケジューラ は(Asynchronous Programming with the Reactive Framework and the Task Parallel Library — Part 3から)ContinueWith(...)の最初の呼び出し時に割り当てられます。

ありがたいことに、あなたはINotifyCompletionから独自のクラスの導出を実装して、タスクが(await anything;から)OnCompleted(Action continuation)方法で使用を開始する必要があり、スケジューラを設定するには、await SomeTask.ConfigureAwait(false)と同様のパターンで、それを使って少しのawaitの動作をカスタマイズすることができます。ここで

は、使用される:ここでは

TaskCompletionSource<object> source = new TaskCompletionSource<object>(); 

    public async Task Foo() { 
     // Force await to schedule the task on the supplied scheduler 
     await SomeAsyncTask().ConfigureScheduler(scheduler); 
    } 

    public Task SomeAsyncTask() { return source.Task; } 

OnCompletedにおける重要な部分でのタスクの拡張メソッドを使用してConfigureSchedulerの単純な実装です:

public static class TaskExtension { 
    public static CustomTaskAwaitable ConfigureScheduler(this Task task, TaskScheduler scheduler) { 
     return new CustomTaskAwaitable(task, scheduler); 
    } 
} 

public struct CustomTaskAwaitable { 
    CustomTaskAwaiter awaitable; 

    public CustomTaskAwaitable(Task task, TaskScheduler scheduler) { 
     awaitable = new CustomTaskAwaiter(task, scheduler); 
    } 

    public CustomTaskAwaiter GetAwaiter() { return awaitable; } 

    public struct CustomTaskAwaiter : INotifyCompletion { 
     Task task; 
     TaskScheduler scheduler; 

     public CustomTaskAwaiter(Task task, TaskScheduler scheduler) { 
      this.task = task; 
      this.scheduler = scheduler; 
     } 

     public void OnCompleted(Action continuation) { 
      // ContinueWith sets the scheduler to use for the continuation action 
      task.ContinueWith(x => continuation(), scheduler); 
     } 

     public bool IsCompleted { get { return task.IsCompleted; } } 
     public void GetResult() { } 
    } 
} 

ここのようにコンパイルしますワーキングサンプルがありますコンソールアプリケーション:

using System; 
using System.Collections.Generic; 
using System.Runtime.CompilerServices; 
using System.Threading.Tasks; 

namespace Example { 
    class Program { 
     static TaskCompletionSource<object> source = new TaskCompletionSource<object>(); 
     static TaskScheduler scheduler = new CustomTaskScheduler(); 

     static void Main(string[] args) { 
      Console.WriteLine("Main Started"); 
      var task = Foo(); 
      Console.WriteLine("Main Continue "); 
      // Continue Foo() using CustomTaskScheduler 
      source.SetResult(null); 
      Console.WriteLine("Main Finished"); 
     } 

     public static async Task Foo() { 
      Console.WriteLine("Foo Started"); 
      // Force await to schedule the task on the supplied scheduler 
      await SomeAsyncTask().ConfigureScheduler(scheduler); 
      Console.WriteLine("Foo Finished"); 
     } 

     public static Task SomeAsyncTask() { return source.Task; } 
    } 

    public struct CustomTaskAwaitable { 
     CustomTaskAwaiter awaitable; 

     public CustomTaskAwaitable(Task task, TaskScheduler scheduler) { 
      awaitable = new CustomTaskAwaiter(task, scheduler); 
     } 

     public CustomTaskAwaiter GetAwaiter() { return awaitable; } 

     public struct CustomTaskAwaiter : INotifyCompletion { 
      Task task; 
      TaskScheduler scheduler; 

      public CustomTaskAwaiter(Task task, TaskScheduler scheduler) { 
       this.task = task; 
       this.scheduler = scheduler; 
      } 

      public void OnCompleted(Action continuation) { 
       // ContinueWith sets the scheduler to use for the continuation action 
       task.ContinueWith(x => continuation(), scheduler); 
      } 

      public bool IsCompleted { get { return task.IsCompleted; } } 
      public void GetResult() { } 
     } 
    } 

    public static class TaskExtension { 
     public static CustomTaskAwaitable ConfigureScheduler(this Task task, TaskScheduler scheduler) { 
      return new CustomTaskAwaitable(task, scheduler); 
     } 
    } 

    public class CustomTaskScheduler : TaskScheduler { 
     protected override IEnumerable<Task> GetScheduledTasks() { yield break; } 
     protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return false; } 
     protected override void QueueTask(Task task) { 
      TryExecuteTask(task); 
     } 
    } 
} 
+3

これを行うには*可能ですが、一般にプロダクションコードでは良いアイデアはありません。オリジナルのCTPは、同じように動作する 'SwitchTo'演算子を持っていましたが、構造化されていないプログラムフローを奨励したため削除され、複雑化と落とし穴が発生しました。たとえば、 'try'で修正された' await'を使用すると、 'finally'ブロックは元のスケジューラーまたは変更されたブロックで実行できる必要があります(そして' finally'コードはスケジューラーを切り替えることができません'await'は許可されません)。 –

+0

@StephenCleary catch/finallyブロックが(C#6)を待つことができるようになりましたが、それはまだ悪い考えですか? – Kryptos

+0

@Kryptos:はい、それでもやはり悪い考えです。 'catch' /' finally'で 'await'を有効にしても、' SwitchTo'動作に影響はありません。 –

2

このメソッドコールに適合することができますか:

await Task.Factory.StartNew(
     () => { /* to do what you need */ }, 
     CancellationToken.None, /* you can change as you need */ 
     TaskCreationOptions.None, /* you can change as you need */ 
     customScheduler);