async/await 在 WinForms 中调试:system.invalidoperationexception 跨线程操作无效
async/await debugging in WinForms: system.invalidoperationexception cross-thread operation not valid
当 starting/running 您的应用程序在 Visual Studio 2017 中通过 Ctrl+F5(不调试启动)并使用 async/await 进行 winforms 的控件事件处理。例如,按钮单击事件处理,您可以访问这些控件属性以进行 read/write 操作,但是当您通过 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) ...
代码构建。
问题:是否有any/more优雅的方法来避免使用 .InvokeRequired without conditional compilation presented in the following code sample片段:
#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
代码构造是否是最 optimal/recommended 的解决方案?
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);
});
}
进度更新,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);
}
});
}
当 starting/running 您的应用程序在 Visual Studio 2017 中通过 Ctrl+F5(不调试启动)并使用 async/await 进行 winforms 的控件事件处理。例如,按钮单击事件处理,您可以访问这些控件属性以进行 read/write 操作,但是当您通过 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) ...
代码构建。
问题:是否有any/more优雅的方法来避免使用 .InvokeRequired without conditional compilation presented in the following code sample片段:
#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
代码构造是否是最 optimal/recommended 的解决方案?
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);
});
}
进度更新,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);
}
});
}