【问题标题】:BackgroundWorker.RunWorkCompleted - cannot rethrow exceptionBackgroundWorker.RunWorkCompleted - 不能重新抛出异常
【发布时间】:2012-10-24 03:02:40
【问题描述】:

我正在为 WCF 调用编写某种包装器(使用 BackgroundWorker),以防止 GUI 在调用过程中冻结。它主要按应有的方式工作,但是当 WCF 调用引发异常时,我遇到了 BackgroundWorker 的问题。如果 DoWork 中发生异常,我可以在 RunWorkCompleted 中检测到它,但将其重新扔到 GUI 中不起作用。我读过很多帖子,有人提到这应该可行。

包装器代码(请注意,WCF 调用由抛出的异常表示):

private void GetSomething(Action<IEnumerable<int>> completedAction)
{
    BackgroundWorker b = new BackgroundWorker();

    b.DoWork += (s, evt) => { throw new Exception(); evt.Result = new List<int> { 1, 2, 3 }; };

    b.RunWorkerCompleted += (s, evt) =>
    {
        if (evt.Error == null && completedAction != null)
        {
           completedAction((IEnumerable<int>)evt.Result);
        }
        else if(evt.Error != null)
        {
           throw evt.Error;
        }
    };

    b.RunWorkerAsync();
}

在 Windows 窗体中调用代码:

private void button3_Click(object sender, EventArgs e)
{
    try
    {
        GetSomething(list =>
        {
           foreach (int i in list)
           {
              listView1.Items.Add(new ListViewItem(i.ToString()));
           }
        });
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

在调试时,我得到:

  1. “在 DoWork 中引发了 'System.Exception' 类型的异常”
  2. “抛出 'System.Exception' 类型的异常”在 throw evt.Error
  3. Main 方法中的 Application.Run(new Form1()) 中的“TargetInvocationException 未处理”

我做错了什么?我想在 Windows 窗体中捕获异常。

【问题讨论】:

  • 您应该在 RunWorkerCompleted 中处理异常。您是否有任何具体原因要重新抛出。
  • @CodeIgnoto:我的想法是将这个决定推给调用者。我不想使用这个包装器中的消息框。它应该尽可能与 GUI 无关
  • 根据我的经验,在 RunWorkerCompleted 代码中抛出的任何异常都会导致应用程序崩溃。所以去任务或在那里处理它。如果您在启动时使用 while(true)Application.DoEvents() 而不是 Application.Run() ,则可以尝试在 doevents 周围捕获,它会抓住您刚刚重新抛出的异常。但是某些代码可能无法通过这种“消息”循环按预期运行。

标签: c# exception backgroundworker


【解决方案1】:

你应该改变这个:

throw evt.Error;

到这里:

MessageBox.Show(evt.Error.Message);

您的异常当前未处理,因为RunWorkerCompleted 处理程序稍后运行。它没有在 button3_Click 中的 try/catch 中运行。

【讨论】:

    【解决方案2】:

    事件b.RunWorkerCompleted 是您应该进行错误处理的地方。您可以传入Action&lt;Exception&gt; 来进行错误处理,例如

    private void GetSomething(Action<IEnumerable<int>> completedAction, Action<Exception> exceptionAction)
    {
        BackgroundWorker b = new BackgroundWorker();
    
        b.DoWork += (s, evt) => { throw new Exception(); evt.Result = new List<int> { 1, 2, 3 }; };
    
        b.RunWorkerCompleted += (s, evt) =>
        {
            if (evt.Error == null && completedAction != null)
                completedAction((IEnumerable<int>)evt.Result);
            else if(evt.Error != null)
                exceptionAction(evt.Error);
        };
    
        b.RunWorkerAsync();
    }
    

    但是,这往往会变得丑陋。如果您使用 .Net 4 或 4.5,您可以使用 Tasks。 Task&lt;TResult&gt; 正是针对这种情况创建的:

    Task<IEnumerable<int>> GetSomething()
    {
        return Task.Factory.StartNew(() => { 
            Thread.Sleep(2000);
            throw new Exception(); 
            return (new List<int> { 1, 2, 3 }).AsEnumerable(); 
            });
    }
    

    Task 基本上是一个带有

    的信号结构
    • .Result 属性
    • .Exception 属性
    • .ContinueWith() 方法

    ContinueWith() 内,您可以检查Task 是否处于故障状态(抛出异常)。

    你可以像这样使用它

        private void button3_Click(object sender, EventArgs e)
        {
            GetSomething()
                .ContinueWith(task =>
                    {
                        if (task.IsCanceled)
                        {
                        }
                        else if (task.IsFaulted)
                        {
                            var ex = task.Exception.InnerException;
                            MessageBox.Show(ex.Message);
                        }
                        else if (task.IsCompleted)
                        {
                            var list = task.Result;
                            foreach (int i in list)
                            {
                                listView1.Items.Add(new ListViewItem(i.ToString()));
                            }
                        }
                    });
        }
    

    如果您使用 .Net 4.5 和 C#5(您需要 VS2012 或 VS2010 和 Async CTP),您甚至可以求助于 asyncawait 之类的

        private async void button3_Click(object sender, EventArgs e)
        {
            try
            {
                var list = await GetSomething();
                foreach (int i in list)
                {
                    listView1.Items.Add(new ListViewItem(i.ToString()));
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    

    ...所有的魔法都是由编译器完成的。请注意,您可以照常使用try catch

    【讨论】:

    • 你好。我现在试过了。第一个问题是“foreach (int i in list)”无法编译,因为 GetSomething 的返回类型是一个任务。将其更改为迭代 task.Result 后,我​​的 GUI 在调用期间被阻塞,并且未捕获到异常。
    • 对不起。你是绝对正确的。 ContinueWith 的例子是无稽之谈。我纠正了它。但是,如果您使用Task.Factory.StartNew(),则不应阻止 GUI。
    • 我会试试这个,谢谢。实际上,我认为您传递 Action 的第一个解决方案是最简单的。这将从 GUI 中抽象出许多细节。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-10
    • 2012-06-29
    • 1970-01-01
    • 2011-06-27
    • 1970-01-01
    相关资源
    最近更新 更多