c# - 在另一个线程上捕获异常?

标签 c# multithreading winforms invoke synchronizationcontext

在开发 winform 应用程序时,您通常需要调用主 GUI 线程来执行 GUI 工作。

Invoke 如今已过时(如果我没看错的话),您应该改为使用 SynchronizationContext

问题是:我如何处理异常?我注意到有时在“同步/调用”线程上抛出的异常会丢失?

我确实使用了 Application.ThreadExceptionAppDomain.CurrentDomain.UnhandledException 但这没有帮助吗?

最佳答案

首先,Synchronization Context 并不是什么新鲜事物,它自 .NET 2.0 以来就一直存在。它与异常处理特别是无关。它也不会使 Control.Invoke 过时。事实上,WinFormsSynchronizationContext 是 WinForms 的同步上下文实现,它使用 Control.BeginInvoke 进行 PostControl.Invoke 用于 Send 方法。

How do I handle exceptions? I have notice that sometimes the exception thrown on the "synced/invoked" thread is lost?

这里的“有时”背后有一个有据可查的行为。 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 运行的消息循环的 future 迭代时执行。

要能够处理由异步回调 抛出的异常,您需要实际观察异步操作的完成。在 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());
        }
    });
}

关于c# - 在另一个线程上捕获异常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21885216/

相关文章:

c# - Windows 窗体 : Obtain window size to fit a picture box containing an image

c# - 在 linq 中返回所有具有相同名称的 xml 项

c# - 如何为各种构造函数做Activator.CreateInstance?

c# - ASP.NET MVP 注入(inject)服务依赖

java - Java 中阻塞线程的监控和终止

c# - 在带有参数的winforms webbrowser控件中从JS调用C#方法时出错

c# - DotNet 的消息队列抽象

python - 在 python 中练习线程

java - 使用java中的AtomicReference获取/设置缓存中的值

c# - 使用正则表达式以任意顺序查找两个字符串