在开发winform应用程序时,通常只需调用以获取主GUI线程来完成GUI工作。
Invoke
今天已过时(如果我读的正确),而是假设您使用SynchronizationContext
代替。
问题是:如何处理异常?我注意到有时在“同步/调用”线程上抛出的异常会丢失吗?
我确实使用Application.ThreadException
和AppDomain.CurrentDomain.UnhandledException
,但这没有帮助?
答案 0 :(得分:6)
首先,同步上下文并不是什么新东西,它自.NET 2.0以来一直存在。它与具体无关,具有异常处理功能。它也没有使Control.Invoke
过时。实际上,WinFormsSynchronizationContext
是WinForms的同步上下文实现,对Control.BeginInvoke
使用Post
,Control.Invoke
方法使用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);
}
});
}
使用SynchronizationContext
的好处是解除WinForms细节的分离。对于可能由WinForms,WPF,Windows Phone,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());
}
});
因此,使用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 });
}
}));
});
}
使用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);
});
最后,使用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());
}
});
}
答案 1 :(得分:0)
ui线程没有自动方法从其他线程捕获异常。
1)在UI类中创建一个设计为在UI线程上运行的方法,例如HandleExceptionFromThread(Exception ex);
2)从ui线程中获取SynchronizationContext。你可以通过调用SynchronizationContext.Current来获得它。
3)将在第二个线程上运行的方法需要将SynchronizationContext作为参数接收。您可能需要从对象到SyncrhonizationContact进行一些动态转换,但它不应该太难。
4)当捕获异常时,同步调用uiContext.Send(HandleExceptionFromThead,ex)或异步调用uiContext.Post(HandleExceptionFromThead,ex),将异常发送到UI线程中要处理的方法。 / p>
以下是我想象的一些示例代码。
public partial class Form1 : Form
{
.....
public void HandleExceptionFromThread(Exception ex)
{
MessageBox.Show(ex.Message);
}
public void ButtonClickToRunThread(object sender, System.EventArgs e)
{
var syncContext = SynchronizationContext.Current;
Task task = new Task((state)=>
{
SynchronizationContext uiContext = state as SynchronizationContext;
try
{
...
}
catch(Exception ex)
{
uiContext.Post(HandleExceptionFromThread, ex);
}
}, syncContext);
task.Start();
}
}