【问题标题】:TargetInvokationException in Application.Run(new Form1());Application.Run(new Form1()) 中的 TargetInvokationException;
【发布时间】:2016-05-25 04:39:17
【问题描述】:

我正在尝试通过按开始按钮来迭代 for 循环并通过按停止按钮来停止它。我正在使用await Task.Run(() => 它以预期的方式工作。但是当我再次按下开始按钮时,我在Application.Run(new Form1()); 中得到了 TargetInvokationException。

下面是我的代码

using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CancellationTest
{
    public partial class Form1 : Form
    {
        private readonly SynchronizationContext synchronizationContext;
        private DateTime previousTime = DateTime.Now;

        CancellationTokenSource cts = new CancellationTokenSource();

        public Form1()
        {
            InitializeComponent();
            synchronizationContext = SynchronizationContext.Current;
        }

        private async void ButtonClickHandlerAsync(object sender, EventArgs e)
        {
            button1.Enabled = false;
            var count = 0;

            CancellationToken token = cts.Token;

            await Task.Run(() =>
            {
                try
                {
                    for (var i = 0; i <= 5000000; i++)
                    {
                        token.ThrowIfCancellationRequested();

                        UpdateUI(i);
                        count = i;
                    }
                }
                catch (System.OperationCanceledException)
                {
                    MessageBox.Show("Canceled");
                }
            }, token);

            label1.Text = @"Counter " + count;
            button1.Enabled = true;
        }

        public void UpdateUI(int value)
        {
            var timeNow = DateTime.Now;

            if ((DateTime.Now - previousTime).Milliseconds <= 50) return;

            synchronizationContext.Post(new SendOrPostCallback(o =>
            {
                label1.Text = @"Counter " + (int)o;
            }), value);

            previousTime = timeNow;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            cts.Cancel();
        }
    }
}

谁能解释为什么会发生这种情况以及如何解决这个问题。

【问题讨论】:

  • 修复未处理的异常诊断,不惜一切代价。对于这样的异常,您总是想知道 InnerException。单击异常助手对话框中的详细信息链接。现在很容易看出 CancellationToken 已经过时了。

标签: c# multithreading winforms cancellation cancellationtokensource


【解决方案1】:

谁能解释一下为什么会这样

TargetInvokationException 是包装器类型异常,主要信息包含在您未显示的InnerException 属性中。查看代码,很可能它是OperationCanceledException,是由将已取消的令牌传递给Task.Run 引起的。

一般而言,您不应重复使用 CancellationTokenSource 实例 - 请参阅文档备注部分中的实现协作取消模型的一般模式

您还应该使用try/catch 保护整个块,而不仅仅是Task.Run 正文。并在开始时初始化所有涉及的状态成员,并在最后进行必要的清理。

所以正确的代码可能是这样的:

private DateTime previousTime;
private CancellationTokenSource cts;

private async void ButtonClickHandlerAsync(object sender, EventArgs e)
{
    button1.Enabled = false;
    var count = 0;
    previousTime = DateTime.Now;
    cts = new CancellationTokenSource();
    try
    {
        CancellationToken token = cts.Token;
        await Task.Run(() =>
        {
            for (var i = 0; i <= 5000000; i++)
            {
                token.ThrowIfCancellationRequested();
                UpdateUI(i);
                count = i;
            }
        }, token);
    }
    catch (System.OperationCanceledException)
    {
        MessageBox.Show("Canceled");
    }
    finally
    {
        cts.Dispose();
        cts = null;
    }
    label1.Text = @"Counter " + count;
    button1.Enabled = true;
}

并确保仅在cts != null 时启用Cancel 按钮或检查点击处理程序中的该条件以避免NRE。

【讨论】:

  • catch (System.OperationCanceledException) 可以吗。我个人的感觉是这可以通过适当的方式完成。有什么想法吗?
  • 为什么不呢 - 它和其他任何情况一样都是例外。如果您有嵌套任务,则不应捕获它以让正确的任务取消,但在这里您处于顶级调用 (async void) 中,这意味着没有代码可以捕获您的异常。事实上,你应该像上面那样在事件处理程序中捕获所有异常,否则你的应用程序将会崩溃。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-07
  • 1970-01-01
  • 2011-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多