TPLでは、例外が例外によってスローされた場合仕事その例外はキャプチャされ、に保存されます。Task.Exceptionその後、のすべての規則に従います。観察された例外。それが観察されないならば、それは最終的にファイナライザスレッドに投げ直され、プロセスをクラッシュさせます。
タスクがその例外を捕捉して代わりにそれを伝播させるのを防ぐ方法はありますか?
私が興味を持っているタスクはすでにUIスレッド上で実行されているでしょう(提供:TaskScheduler.FromCurrentSynchronizationContextそして、私はそれが私の既存の人によって処理されることができるように例外をエスケープしたいですApplication.ThreadException取引。
基本的には、タスク内の未処理の例外がボタンクリックハンドラ内の未処理の例外のように動作するようにします。ただちにUIスレッドに伝播し、ThreadExceptionによって処理されます。
Ok Joe ...約束されているとおり、これをカスタムで一般的に解決する方法があります。TaskScheduler
サブクラス私はこの実装をテストしました、そしてそれは魅力のように働きます。忘れないで見たい場合はデバッガをアタッチすることはできませんApplication.ThreadException
実際に発砲する!
このカスタムTaskScheduler実装は特定のものに結び付けられています。SynchronizationContext
「出生時」に、各着信を受け取りますTask
それが実行する必要があるということは、論理的なTask
障害が発生し、それが発生すると、Post
s 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
これは再スローしても意味がありません。
私の実際のコードでは、最初のタスクと複数の継続という複数のタスクがあります。それらのうちのどれか(潜在的に複数)が例外を受けた場合、私はAggregateException
UIスレッドに戻ります。これを処理する方法は次のとおりです。
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
にそれを通らないThreadException
handler:代わりにのみ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();