Отладка async/await в WinForms: недопустимая многопотоковая операция system.invalidoperationexception
При запуске / запуске приложения из Visual Studio 2017 с помощью Ctrl+F5 (Запуск без отладки) и использовании async/await для обработки событий элементов управления 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 ответ
Для обновления прогресса используйте 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);
}
});
}