在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);
});
}
答案 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);
}
});
}