TPL에서 예외가 Throw되면태스크, 그 예외는 캡쳐되어Task.Exception, 다음에있는 모든 규칙을 따릅니다.관측 예외. 결코 관찰되지 않는다면, 결국 파이널 라이저 스레드에서 다시 발생하고 프로세스를 중단합니다.
태스크가 해당 예외를 포착하지 못하도록 방지하는 방법이 있습니까?
관심있는 작업은 UI 스레드에서 이미 실행되고있을 것입니다 (TaskScheduler.FromCurrentSynchronizationContext), 예외를 이스케이프 처리하여 기존의 내 처리 방법으로 처리 할 수있게하려고합니다.Application.ThreadException거래.
나는 기본적으로 Button-Click 핸들러에서 처리되지 않은 예외처럼 동작하는 Task에서 처리되지 않은 예외를 원합니다. UI 스레드에서 즉시 전파되고 ThreadException에 의해 처리됩니다.
Ok Joe ... 약속대로이 문제를 일반적으로 사용자 정의로 해결할 수 있습니다.TaskScheduler
아강. 이 구현을 테스트 한 결과 매력처럼 작동합니다.잊지 마라.보고 싶으면 디버거를 연결할 수 없습니다.Application.ThreadException
실제로 발사!
이 사용자 정의 TaskScheduler 구현은 특정SynchronizationContext
"출생"시에Task
그것을 실행해야 할 필요가있다.Task
결함이 발생하면 그 불이Post
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 ...
});
또 다른 방법은 사용자 지정 인스턴스를 만드는 것입니다.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은 예외를 멈추지 않고 버튼 클릭 처리기에서 처리되지 않은 예외 인 것처럼 정상적으로 전파 할 수 있습니다.
하나의 여분의 주름은 제가 전화하고 싶지 않다는 것입니다.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
실패한 태스크가있는 경우
처음에 나는 계속 작업 중 하나가 다음과 같은 것을 사용했다면 걱정했다.TaskContinuationOptions.OnlyOnRanToCompletion
, 첫 번째 작업이 오류가 발생하면WaitAll
통화가 끊어 질 수 있습니다 (연속 작업이 실행되지 않으므로,WaitAll
실행을 기다리는 것을 차단합니다). 그러나 TPL 설계자는 그보다 더 개연성이있었습니다. 왜냐하면 계속 작업이OnlyOn
또는NotOn
플래그는 연속 작업이Canceled
상태를 차단하지 않습니다.WaitAll
.
다중 작업 버전을 사용할 때WaitAll
호출은AggregateException
,하지만 그건AggregateException
그것으로 끝나지 않는다.ThreadException
처리기 : 대신하나내부 예외가 전달됩니다.ThreadException
. 따라서 여러 작업에서 예외가 발생하면 그 중 하나만 스레드 예외 처리기에 도달합니다. 왜 그런지는 확실치 않지만, 알아 내려고하고 있습니다.
이러한 예외가 주 스레드의 예외처럼 전파되는 것을 인식하고있는 방법은 없습니다. 왜 당신이 연결하고있는 핸들러에 연결하지 않는 것일까 요?Application.ThreadException
에TaskScheduler.UnobservedTaskException
게다가?
이 양복 같은 것이 있습니까?
public static async void Await(this Task task, Action action = null)
{
await task;
if (action != null)
action();
}
runningTask.Await();