WinForms中的async / await调试:system.invalidoperationexception跨线程操作无效

时间:2017-07-31 21:10:07

标签: winforms debugging async-await runtime-error cross-thread

在Visual Studio 2017中通过Ctrl + F5启动/运行应用程序(启动无调试)并使用async / await for winforms'控制事件处理。例如,按钮单击事件处理,您可以访问这些控件属性以进行读/写操作,但是当您通过F5启动应用程序时,您会收到运行时错误消息:

system.invalidoperationexception cross-thread operation not valid: 
Control '{{controlName}}' accessed from a thread other than the thread it was created on.'

要解决此问题,您必须使用众所周知的

if (this.InvokeRequired) ...

代码构造。

问题:是否有任何/更优雅的方法可以避免在以下代码示例片段中使用.InvokeRequired 而无需条件编译:

#define DEBUG_TRACE

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

namespace WindowsFormsAppToTestAsyncAwait
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }
        private
            async
            void cmdTest_Click(object sender, EventArgs e)
        {
            await Task.Run(() =>
            {
#if DEBUG_TRACE
                inv(()=>
#endif
                txtTest.Text = ""
#if DEBUG_TRACE
                )
#endif
                ;
                for (long i = 1; i < 100000000; i++)
                {
                    if (i % 10000000 == 1)
#if DEBUG_TRACE
                        inv(() =>
#endif
                        txtTest.Text =
                            txtTest.Text +
                                i.ToString() + System.Environment.NewLine
#if DEBUG_TRACE
                                )
#endif
                                ;
                }

            });
        }

 #if DEBUG_TRACE
        private void inv(Action a)
        {
            if (this.InvokeRequired) this.Invoke (a); else a();
        }
 #endif
 }
}

更新

问题:以下代码示例statsProgress代码构建是否是最佳/推荐的解决方案?

private async void cmdTest_Click(object sender, EventArgs e)
{
    double runningSum = 0;
    long totalCount = 0;
    double average = 0;

    IProgress<long> progress = new Progress<long>(i =>
    {
        txtTest.Text = txtTest.Text + i.ToString() + System.Environment.NewLine;
    });

    IProgress<object> statsProgress = new Progress<object>(o =>
    {
        txtRunningSum.Text = runningSum.ToString();
        txtTotalCount.Text = totalCount.ToString();
        txtAverage.Text = average.ToString();
    });

    txtTest.Text = "";

    await Task.Run(() =>
    {
        for (long i = 1; i < 100000000; i++)
        {
            runningSum += i;
            totalCount += 1;
            average = runningSum / totalCount;

            if (i % 10000000 == 1)  progress?.Report(i);
            // in general case there could be many updates of controls' values
            // from within this awaited Task with every update issued/fired
            // on different steps of this long running cycle
            if (i % (10000000 / 2) == 1) statsProgress?.Report(default(object));
        }
    });
}

更新2

以下是最终解决方案:

internal struct UpdateStats
{
    internal double RunningSum;
    internal long TotalCount;
    internal double Average;
}
private async void cmdTest_Click(object sender, EventArgs e)
{
    UpdateStats stats = new UpdateStats();
    IProgress<long> progress = new Progress<long>(i =>
    {
        txtTest.Text = txtTest.Text + i.ToString() + System.Environment.NewLine;
    });

    IProgress<UpdateStats> statsProgress = new Progress<UpdateStats>(o =>
    {
        txtRunningSum.Text = o.RunningSum.ToString();
        txtTotalCount.Text = o.TotalCount.ToString();
        txtAverage.Text = o.Average.ToString();
    });

    txtTest.Text = "";

    await Task.Run(() =>
    {
        const int MAX_CYCLE_COUNT = 100000000;
        for (long i = 1; i <= MAX_CYCLE_COUNT; i++)
        {
            stats.RunningSum += i;
            stats.TotalCount += 1;
            stats.Average = stats.RunningSum / stats.TotalCount;

            if (i % 10000000 == 1) progress?.Report(i);
            // in general case there could be many updates of controls' values
            // from within this awaited Task with every update issued/fired
            // on different steps of this long running cycle
            if (i % (10000000 / 2) == 1) statsProgress?.Report(stats);
        }

        progress?.Report(MAX_CYCLE_COUNT);
        statsProgress?.Report(stats);
    });
}

1 个答案:

答案 0 :(得分:3)

有关进度更新,use IProgress<T>/Progress<T>

private async void cmdTest_Click(object sender, EventArgs e)
{
  IProgress<int> progress = new Progress<int>(i =>
  {
      txtTest.Text = txtTest.Text + i.ToString() + System.Environment.NewLine;
  });

  txtTest.Text = "";
  await Task.Run(() =>
  {
    for (long i = 1; i < 100000000; i++)
    {
      if (i % 10000000 == 1)
        progress?.Report(i);
    }
  });
}