Ban*_*hee 3 c# multithreading invoke winforms synchronizationcontext
在开发winform应用程序时,通常只需调用以获取主GUI线程来完成GUI工作.
Invoke今天已经过时了(如果我读的正确),相反你应该使用SynchronizationContext相反的.
问题是:我如何处理异常?我注意到有时在"同步/调用"线程上抛出的异常会丢失?
我确实使用了Application.ThreadException,AppDomain.CurrentDomain.UnhandledException但这没有用?
首先,同步上下文并不是什么新东西,它自.NET 2.0以来一直存在.它无关专门与异常处理.它也没有Control.Invoke过时.实际上,WinFormsSynchronizationContext这是WinForms的同步上下文实现,用于Control.BeginInvokefor Post和Control.Invokefor Send方法.
我该如何处理异常?我注意到有时在"同步/调用"线程上抛出的异常会丢失?
这里"有时"背后有一个记录良好的行为.Control.Invoke是一个同步调用,它将异常从回调内部传播到调用线程:
int Test()
{
throw new InvalidOperationException("Surpise from the UI thread!");
}
void Form_Load(object sender, EventArgs e)
{
// UI thread
ThreadPool.QueueUserWorkItem(x =>
{
// pool thread
try
{
this.Invoke((MethodInvoker)Test);
}
catch (Exception ex)
{
Debug.Print(ex.Message);
}
});
}
Run Code Online (Sandbox Code Playgroud)
使用的好处SynchronizationContext是解除WinForms细节的分离.这是有道理的便携式库,它可能会通过的WinForms,WPF,Windows手机,Xamarin或任何其他客户端使用:
// UI thread
var uiSynchronizationContext = System.Threading.SynchronizationContext.Current;
if (uiSynchronizationContext == null)
throw new NullReferenceException("SynchronizationContext.Current");
ThreadPool.QueueUserWorkItem(x =>
{
// pool thread
try
{
uiSynchronizationContext.Send(s => Test(), null);
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
}
});
Run Code Online (Sandbox Code Playgroud)
因此,使用Control.Invoke(或SynchronizationContext.Send)您可以选择处理调用线程上的异常.您没有Control.BeginInvoke(或SynchronizationContext.Post),设计和常识的选择.那是因为Control.BeginInvoke它是异步的,它会将回调排队,以便在将来运行的消息循环迭代时执行Application.Run.
为了能够处理异步回调抛出的异常,您需要实际观察异步操作的完成情况.在C#5.0之前,您可以使用事件或Task.ContinueWith为此.
使用活动:
class ErrorEventArgs : EventArgs
{
public Exception Exception { get; set; }
}
event EventHandler<ErrorEventArgs> Error = delegate { };
void Form_Load(object sender, EventArgs e)
{
this.Error += (sError, eError) =>
// handle the error on the UI thread
Debug.Print(eError.Exception.ToString());
ThreadPool.QueueUserWorkItem(x =>
{
this.BeginInvoke(new MethodInvoker(() =>
{
try
{
Test();
}
catch (Exception ex)
{
// fire the Error event
this.Error(this, new ErrorEventArgs { Exception = ex });
}
}));
});
}
Run Code Online (Sandbox Code Playgroud)
使用ContinueWith:
ThreadPool.QueueUserWorkItem(x =>
{
var tcs = new TaskCompletionSource<int>();
uiSynchronizationContext.Post(s =>
{
try
{
tcs.SetResult(Test());
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, null);
// observe the completion,
// only if there's an error
tcs.Task.ContinueWith(task =>
{
// handle the error on a pool thread
Debug.Print(task.Exception.ToString());
}, TaskContinuationOptions.OnlyOnFaulted);
});
Run Code Online (Sandbox Code Playgroud)
最后,使用C#5.0,您可以使用async/await和处理异步抛出的异常,同样try/catch具有同步调用的便利性:
int Test()
{
throw new InvalidOperationException("Surpise from the UI thread!");
}
async void Form_Load(object sender, EventArgs e)
{
// UI thread
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
await Task.Run(async () =>
{
// pool thread
try
{
await Task.Factory.StartNew(
() => Test(),
CancellationToken.None,
TaskCreationOptions.None,
uiTaskScheduler);
}
catch (Exception ex)
{
// handle the error on a pool thread
Debug.Print(ex.ToString());
}
});
}
Run Code Online (Sandbox Code Playgroud)