33

次の同期コードを想定します。

try
{
    Foo();
    Bar();
    Fubar();
    Console.WriteLine("All done");
}
catch(Exception e) // For illustration purposes only. Catch specific exceptions!
{
    Console.WriteLine(e);
}

ここで、これらすべてのメソッドにAsyncの対応物があり、何らかの理由でそれらを使用する必要があると仮定します。したがって、単にすべてを新しいタスクでラップすることはオプションではありません。
どうすれば同じ動作を実現できますか?
「同じ」とは、次のことを意味します。

  1. 例外がスローされた場合は、例外のハンドラーを実行します。
  2. 例外がスローされた場合は、次のメソッドの実行を停止します。

私が思いついた唯一のことは恐ろしいことです:

var fooTask = FooAsync();
fooTask.ContinueWith(t => HandleError(t.Exception),
                     TaskContinuationOptions.OnlyOnFaulted);
fooTask.ContinueWith(
    t =>
    {
        var barTask = BarAsync();
        barTask.ContinueWith(t => HandleError(t.Exception),
                             TaskContinuationOptions.OnlyOnFaulted);
        barTask.ContinueWith(
            t =>
            {
                var fubarTask = FubarAsync();
                fubarTask.ContinueWith(t => HandleError(t.Exception),
                                       TaskContinuationOptions.OnlyOnFaulted);
                fubarTask.ContinueWith(
                    t => Console.WriteLine("All done"),
                    TaskContinuationOptions.OnlyOnRanToCompletion);
            }, 
            TaskContinuationOptions.OnlyOnRanToCompletion);
    }, 
    TaskContinuationOptions.OnlyOnRanToCompletion);

ご注意ください:

  • .NET 4で動作するソリューションが必要なのでasync/await、問題外です。ただし、それがうまくいく場合は、そのasync/await方法を自由に示してください。
  • TPLを使用する必要はありません。TPLで不可能な場合は、別のアプローチで問題ありません。おそらくReactive Extensionsを使用しますか?
4

5 に答える 5

31

これがどのように機能するかasyncです:

try
{
    await FooAsync();
    await BarAsync();
    await FubarAsync();
    Console.WriteLine("All done");
}
catch(Exception e) // For illustration purposes only. Catch specific exceptions!
{
    Console.WriteLine(e);
}

これは、(プレリリースの) Microsoft.Bcl.Asyncパッケージをインストールした場合、.NET4.0で機能します。


VS2010に固執しているので、StephenToubのバリアントを使用できますThen

public static Task Then(this Task first, Func<Task> next)
{
  var tcs = new TaskCompletionSource<object>();
  first.ContinueWith(_ =>
  {
    if (first.IsFaulted) tcs.TrySetException(first.Exception.InnerExceptions);
    else if (first.IsCanceled) tcs.TrySetCanceled();
    else
    {
      try
      {
        next().ContinueWith(t =>
        {
          if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions);
          else if (t.IsCanceled) tcs.TrySetCanceled();
          else tcs.TrySetResult(null);
        }, TaskContinuationOptions.ExecuteSynchronously);
      }
      catch (Exception exc) { tcs.TrySetException(exc); }
    }
  }, TaskContinuationOptions.ExecuteSynchronously);
  return tcs.Task; 
}

あなたはそれをそのように使うことができます:

var task = FooAsync().Then(() => BarAsync()).Then(() => FubarAsync());
task.ContinueWith(t =>
{
  if (t.IsFaulted || t.IsCanceled)
  {
    var e = t.Exception.InnerException;
    // exception handling
  }
  else
  {
    Console.WriteLine("All done");
  }
}, TaskContinuationOptions.ExcecuteSynchronously);

Rxを使用すると、次のようになります(asyncメソッドがすでに公開されていない場合IObservable<Unit>):

FooAsync().ToObservable()
    .SelectMany(_ => BarAsync().ToObservable())
    .SelectMany(_ => FubarAsync().ToObservable())
    .Subscribe(_ => { Console.WriteLine("All done"); },
        e => { Console.WriteLine(e); });

おもう。私は決してRxマスターではありません。:)

于 2013-01-31T16:51:05.403 に答える
6

完全を期すために、ChrisSinclairによって提案されたヘルパーメソッドを実装する方法は次のとおりです。

public void RunSequential(Action onComplete, Action<Exception> errorHandler,
                          params Func<Task>[] actions)
{
    RunSequential(onComplete, errorHandler,
                  actions.AsEnumerable().GetEnumerator());
}

public void RunSequential(Action onComplete, Action<Exception> errorHandler,
                          IEnumerator<Func<Task>> actions)
{
    if(!actions.MoveNext())
    {
        onComplete();
        return;
    }

    var task = actions.Current();
    task.ContinueWith(t => errorHandler(t.Exception),
                      TaskContinuationOptions.OnlyOnFaulted);
    task.ContinueWith(t => RunSequential(onComplete, errorHandler, actions),
                      TaskContinuationOptions.OnlyOnRanToCompletion);
}

これにより、後続の各タスクは、前のタスクが正常に完了したときにのみ要求されます。は、すでに実行中のタスクを返す
ことを前提としています。Func<Task>

于 2013-01-31T17:38:41.813 に答える
5

ここにあるのは本質的にForEachAsyncです。各非同期アイテムを順番に実行したいが、エラー処理のサポートが必要です。そのような実装の1つを次に示します。

public static Task ForEachAsync(IEnumerable<Func<Task>> tasks)
{
    var tcs = new TaskCompletionSource<bool>();

    Task currentTask = Task.FromResult(false);

    foreach (Func<Task> function in tasks)
    {
        currentTask.ContinueWith(t => tcs.TrySetException(t.Exception.InnerExceptions)
            , TaskContinuationOptions.OnlyOnFaulted);
        currentTask.ContinueWith(t => tcs.TrySetCanceled()
                , TaskContinuationOptions.OnlyOnCanceled);
        Task<Task> continuation = currentTask.ContinueWith(t => function()
            , TaskContinuationOptions.OnlyOnRanToCompletion);
        currentTask = continuation.Unwrap();
    }

    currentTask.ContinueWith(t => tcs.TrySetException(t.Exception.InnerExceptions)
            , TaskContinuationOptions.OnlyOnFaulted);
    currentTask.ContinueWith(t => tcs.TrySetCanceled()
            , TaskContinuationOptions.OnlyOnCanceled);
    currentTask.ContinueWith(t => tcs.TrySetResult(true)
            , TaskContinuationOptions.OnlyOnRanToCompletion);

    return tcs.Task;
}

キャンセルされたタスクのサポートも追加しました。これは、より一般的で、実行するのにほとんど時間がかからなかったためです。

各タスクを前のタスクの続きとして追加し、すべての行に沿って、例外が発生すると最終タスクの例外が設定されるようにします。

使用例は次のとおりです。

public static Task FooAsync()
{
    Console.WriteLine("Started Foo");
    return Task.Delay(1000)
        .ContinueWith(t => Console.WriteLine("Finished Foo"));
}

public static Task BarAsync()
{
    return Task.Factory.StartNew(() => { throw new Exception(); });
}

private static void Main(string[] args)
{
    List<Func<Task>> list = new List<Func<Task>>();

    list.Add(() => FooAsync());
    list.Add(() => FooAsync());
    list.Add(() => FooAsync());
    list.Add(() => FooAsync());
    list.Add(() => BarAsync());

    Task task = ForEachAsync(list);

    task.ContinueWith(t => Console.WriteLine(t.Exception.ToString())
        , TaskContinuationOptions.OnlyOnFaulted);
    task.ContinueWith(t => Console.WriteLine("Done!")
        , TaskContinuationOptions.OnlyOnRanToCompletion);
}
于 2013-01-31T18:26:41.927 に答える
3

2つのタスクを組み合わせるメソッドを作成し、最初のタスクが成功した場合にのみ2番目のタスクを開始できるはずです。

public static Task Then(this Task parent, Task next)
{
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
    parent.ContinueWith(pt =>
    {
        if (pt.IsFaulted)
        {
            tcs.SetException(pt.Exception.InnerException);
        }
        else
        {
            next.ContinueWith(nt =>
            {
                if (nt.IsFaulted)
                {
                    tcs.SetException(nt.Exception.InnerException);
                }
                else { tcs.SetResult(null); }
            });
            next.Start();
        }
    });
    return tcs.Task;
}

次に、タスクを連鎖させることができます。

Task outer = FooAsync()
    .Then(BarAsync())
    .Then(FubarAsync());

outer.ContinueWith(t => {
    if(t.IsFaulted) {
        //handle exception
    }
});

タスクがすぐに開始される場合は、それらをFunc:でラップするだけです。

public static Task Then(this Task parent, Func<Task> nextFunc)
{
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
    parent.ContinueWith(pt =>
    {
        if (pt.IsFaulted)
        {
            tcs.SetException(pt.Exception.InnerException);
        }
        else
        {
            Task next = nextFunc();
            next.ContinueWith(nt =>
            {
                if (nt.IsFaulted)
                {
                    tcs.SetException(nt.Exception.InnerException);
                }
                else { tcs.SetResult(null); }
            });
        }
    });
    return tcs.Task;
}
于 2013-01-31T17:30:31.170 に答える
1

今、私はTPLをあまり使用していないので、これは暗闇の中での刺し傷にすぎません。そして、@ Servyが述べたことを考えると、おそらくこれは完全に非同期で実行されることはありません。しかし、私はそれを投稿すると思いました、そしてそれがマークから外れているならあなたは私を忘却に反対票を投じるか、私はそれを削除することができます(または私たちは修正が必要なものを修正することができます)

public void RunAsync(Action onComplete, Action<Exception> errorHandler, params Action[] actions)
{
    if (actions.Length == 0)
    {
        //what to do when no actions/tasks provided?
        onComplete();
        return;
    }

    List<Task> tasks = new List<Task>(actions.Length);
    foreach(var action in actions)
    {
        Task task = new Task(action);
        task.ContinueWith(t => errorHandler(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
        tasks.Add(task);
    }

    //last task calls onComplete
    tasks[actions.Length - 1].ContinueWith(t => onComplete(), TaskContinuationOptions.OnlyOnRanToCompletion);

    //wire all tasks to execute the next one, except of course, the last task
    for (int i = 0; i <= actions.Length - 2; i++)
    {
        var nextTask = tasks[i + 1];
        tasks[i].ContinueWith(t => nextTask.Start(), TaskContinuationOptions.OnlyOnRanToCompletion);
    }

    tasks[0].Start();
}

そしてそれは次のような使用法を持っているでしょう:

RunAsync(() => Console.WriteLine("All done"),
            ex => Console.WriteLine(ex),
            Foo,
            Bar,
            Fubar);

考え?反対票?:)

(私は間違いなく非同期/待機を好みます)

編集:あなたのコメントに基づいて、Func<Task>これは適切な実装でしょうか?

public void RunAsync(Action onComplete, Action<Exception> errorHandler, params Func<Task>[] actions)
{
    if (actions.Length == 0)
    {
        //what to do when no actions/tasks provided?
        onComplete();
        return;
    }

    List<Task> tasks = new List<Task>(actions.Length);
    foreach (var action in actions)
    {
        Func<Task> nextActionFunc = action;
        Task task = new Task(() =>
        {
            var nextTask = nextActionFunc();
            nextTask.ContinueWith(t => errorHandler(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
            nextTask.Start();
        });
        tasks.Add(task);
    }

    //last task calls onComplete
    tasks[actions.Length - 1].ContinueWith(t => onComplete(), TaskContinuationOptions.OnlyOnRanToCompletion);

    //wire all tasks to execute the next one, except of course, the last task
    for (int i = 0; i <= actions.Length - 2; i++)
    {
        var nextTask = tasks[i + 1];
        tasks[i].ContinueWith(t => nextTask.Start(), TaskContinuationOptions.OnlyOnRanToCompletion);
    }

    tasks[0].Start();
}
于 2013-01-31T17:06:44.607 に答える