TPLでは、例外が例外によってスローされた場合仕事その例外はキャプチャされ、に保存されます。Task.Exceptionその後、のすべての規則に従います。観察された例外。それが観察されないならば、それは最終的にファイナライザスレッドに投げ直され、プロセスをクラッシュさせます。
タスクがその例外を捕捉して代わりにそれを伝播させるのを防ぐ方法はありますか?
私が興味を持っているタスクはすでにUIスレッド上で実行されているでしょう(提供:TaskScheduler.FromCurrentSynchronizationContextそして、私はそれが私の既存の人によって処理されることができるように例外をエスケープしたいですApplication.ThreadException取引。
基本的には、タスク内の未処理の例外がボタンクリックハンドラ内の未処理の例外のように動作するようにします。ただちにUIスレッドに伝播し、ThreadExceptionによって処理されます。
Ok Joe ...約束されているとおり、これをカスタムで一般的に解決する方法があります。TaskSchedulerサブクラス私はこの実装をテストしました、そしてそれは魅力のように働きます。忘れないで見たい場合はデバッガをアタッチすることはできませんApplication.ThreadException実際に発砲する!
このカスタムTaskScheduler実装は特定のものに結び付けられています。SynchronizationContext「出生時」に、各着信を受け取りますTaskそれが実行する必要があるということは、論理的なTask障害が発生し、それが発生すると、Posts SynchronizationContextに戻り、そこでSynchronizationContextから例外をスローします。Taskそれは失敗しました。
public sealed class SynchronizationContextFaultPropagatingTaskScheduler : TaskScheduler
{
    #region Fields
    private SynchronizationContext synchronizationContext;
    private ConcurrentQueue<Task> taskQueue = new ConcurrentQueue<Task>();
    #endregion
    #region Constructors
    public SynchronizationContextFaultPropagatingTaskScheduler() : this(SynchronizationContext.Current)
    {
    }
    public SynchronizationContextFaultPropagatingTaskScheduler(SynchronizationContext synchronizationContext)
    {
        this.synchronizationContext = synchronizationContext;
    }
    #endregion
    #region Base class overrides
    protected override void QueueTask(Task task)
    {
        // Add a continuation to the task that will only execute if faulted and then post the exception back to the synchronization context
        task.ContinueWith(antecedent =>
            {
                this.synchronizationContext.Post(sendState =>
                {
                    throw (Exception)sendState;
                },
                antecedent.Exception);
            },
            TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
        // Enqueue this task
        this.taskQueue.Enqueue(task);
        // Make sure we're processing all queued tasks
        this.EnsureTasksAreBeingExecuted();
    }
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        // Excercise for the reader
        return false;
    }
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return this.taskQueue.ToArray();
    }
    #endregion
    #region Helper methods
    private void EnsureTasksAreBeingExecuted()
    {
        // Check if there's actually any tasks left at this point as it may have already been picked up by a previously executing thread pool thread (avoids queueing something up to the thread pool that will do nothing)
        if(this.taskQueue.Count > 0)
        {
            ThreadPool.UnsafeQueueUserWorkItem(_ =>
            {
                Task nextTask;
                // This thread pool thread will be used to drain the queue for as long as there are tasks in it
                while(this.taskQueue.TryDequeue(out nextTask))
                {
                    base.TryExecuteTask(nextTask);
                }
            },
            null);
        }
    }
    #endregion
}
この実装に関するいくつかの注意/免責事項:
Task取り組まれる。私はこれを読者のための練習として残しておきます。それは難しいことではありません、ちょうど...あなたが求めている機能を実証する必要はありません。さて、今、あなたはこのTaskSchedulerを使用するためのいくつかの選択肢があります:
この方法では、TaskFactoryそのファクトリインスタンスで開始したタスクは、いつでもカスタムを使用します。TaskScheduler。それは基本的に次のようになります。
private static readonly TaskFactory MyTaskFactory = new TaskFactory(new SynchronizationContextFaultPropagatingTaskScheduler());
MyTaskFactory.StartNew(_ =>
{
    // ... task impl here ...
});
もう1つの方法は、カスタムのインスタンスを作成することです。TaskSchedulerそしてそれをに渡すStartNewデフォルトでTaskFactoryタスクを開始するたびに。
private static readonly SynchronizationContextFaultPropagatingTaskScheduler MyFaultPropagatingTaskScheduler = new SynchronizationContextFaultPropagatingTaskScheduler();
Task.Factory.StartNew(_ =>
{
    // ... task impl here ...
},
CancellationToken.None // your specific cancellationtoken here (if any)
TaskCreationOptions.None, // your proper options here
MyFaultPropagatingTaskScheduler);
                    
                                            
私は時々適切に機能する解決策を見つけました。
var synchronizationContext = SynchronizationContext.Current;
var task = Task.Factory.StartNew(...);
task.ContinueWith(task =>
    synchronizationContext.Post(state => {
        if (!task.IsCanceled)
            task.Wait();
    }, null));
これはへの呼び出しをスケジュールします。task.Wait()UIスレッドで。しないのでWaitタスクが既に完了したことがわかるまで、実際にはブロックされません。例外があるかどうかを確認するだけで、発生した場合はスローされます。以来SynchronizationContext.Postコールバックはメッセージループから直接実行されます。Task)、TPLは例外を停止せず、ボタンクリックハンドラで未処理の例外であるかのように、正常に伝播できます。
もう1つのしわは、電話したくないということです。WaitAllタスクがキャンセルされた場合キャンセルされたタスクを待つと、TPLはTaskCanceledExceptionこれは再スローしても意味がありません。
私の実際のコードでは、最初のタスクと複数の継続という複数のタスクがあります。それらのうちのどれか(潜在的に複数)が例外を受けた場合、私はAggregateExceptionUIスレッドに戻ります。これを処理する方法は次のとおりです。
var synchronizationContext = SynchronizationContext.Current;
var firstTask = Task.Factory.StartNew(...);
var secondTask = firstTask.ContinueWith(...);
var thirdTask = secondTask.ContinueWith(...);
Task.Factory.ContinueWhenAll(
    new[] { firstTask, secondTask, thirdTask },
    tasks => synchronizationContext.Post(state =>
        Task.WaitAll(tasks.Where(task => !task.IsCanceled).ToArray()), null));
同じ話:すべてのタスクが完了したら、電話してくださいWaitAllのコンテキスト外Task。タスクはすでに完了しているので、ブロックされることはありません。投げるのは簡単な方法ですAggregateExceptionいずれかのタスクが失敗した場合
最初のうちは、継続タスクの1つが次のようなものを使用しているのではないかと心配しました。TaskContinuationOptions.OnlyOnRanToCompletionそして最初のタスクが失敗し、そしてWaitAllコールがハングアップする可能性があります(継続タスクが実行されないため、私は心配しました)。WaitAllそれが実行されるのを待つのをブロックします)。しかし、それはTPL設計者がそれより賢いということがわかります - もし継続タスクが実行されないならば。OnlyOnまたはNotOnフラグ、継続タスクがCanceled状態、従ってそれは妨げませんWaitAll。
マルチタスクバージョンを使用すると、WaitAll呼び出しがスローされますAggregateException、 でもあのAggregateExceptionにそれを通らないThreadExceptionhandler:代わりにのみ1その内部例外のうちのThreadException。そのため、複数のタスクが例外をスローした場合、そのうちの1つだけがスレッド例外ハンドラに到達します。これがなぜなのかはっきりしませんが、把握しようとしています。
これらの例外がメインスレッドからの例外のように伝播することを認識している方法はありません。ちょうどあなたがにフックしているのと同じハンドラをフックしないでくださいApplication.ThreadExceptionにTaskScheduler.UnobservedTaskException同様に?
このスーツのようなものはありますか?
public static async void Await(this Task task, Action action = null)
{
   await task;
   if (action != null)
      action();
}
runningTask.Await();