#c# #multithreading
#c# #многопоточность
Вопрос:
Я пытаюсь добавить потоковую обработку в свою программу таким образом, чтобы я не замораживал весь основной поток пользовательского интерфейса при выполнении «дорогостоящей» вычислительной работы.
В настоящее время моя программа выполняет асинхронный запуск, Task
указывающий на функцию с именем startWork()
при нажатии кнопки следующим образом:
async void startParse_Click(object sender, EventArgs e)
{
await Task.Run(() => startWork());
}
Обычно для установки значений я делаю следующее:
niceButton.BeginInvoke(new MethodInvoker(() =>
{
niceButton.Text = "new text";
}));
Однако при получении данных из элементов управления и использовании этих данных за пределами MethodInvoker
у меня возникают небольшие проблемы. Моя цель — выполнить foreach
цикл вокруг моего listView1.Items
, за пределами потока пользовательского интерфейса.
Вот содержимое startWork()
:
void startWork()
{
// Naturally I cannot reference my listView1 control because it is in a
// different thread and is blocked the the "illegal" cross-thread check
int overallProgress = 0;
ListView.ListViewItemCollection items = null;
// This unfortunately doesn't work (MethodInvoker is in a different scope?)
listView1.BeginInvoke( new MethodInvoker(() => {
items = listView1.Items;
}));
int totalItems = items.Count; // "items" isn't recognized
foreach (ListViewItem files in items )
{
// slowwww work
}
}
Я также пытался передать ListView.ListViewItemCollection
в качестве аргумента функции безрезультатно.
Продолжаем получать Cross-thread operation not valid: accessed from a thread other than the thread it was created on
Примечание: Целевой платформой является .NET 4.7 — возможно, в более новых версиях .NET есть лучший / более эффективный метод?
Возможно, мне просто не хватает фундаментального понимания async / tasks, но я предполагаю, что я упускаю из виду что-то важное.
Комментарии:
1. где вычислительная работа, также зачем вообще использовать async в любом из этого, вы все равно просто выгружаете
2. Это то, что вы называете вычислительной работой? в foreach? вы обновляете пользовательский интерфейс в foreach?
3. Вы обновляете пользовательский интерфейс в foreach?
4. Какова природа вашего foreach, можете ли вы обновить свой код тем, что находится в этом цикле
Ответ №1:
Элементы пользовательского интерфейса, включая ListView.ListViewItemCollection
и ListViewItem
, являются «потокоподобными». Это означает, что к ним можно получить доступ только в потоке пользовательского интерфейса.
Для выполнения фоновой работы вам следует передавать только объекты, не связанные с потоком. Например, a List<string>
, а не a ListViewItemCollection
или a List<ListViewItem>
.
async void startParse_Click(object sender, EventArgs e)
{
var items = listView1.Items;
var data = /* extract List<string> from items */
await Task.Run(() => startWork(data));
}
void startWork(List<string> files)
{
int overallProgress = 0;
foreach (var file in files)
{
// slowwww work
}
}
Кроме того, вы не должны использовать BeginInvoke
. Вместо этого используйте IProgress<T>
with Progress<T>
.
Ответ №2:
Вам не нужно перебирать элементы в рабочем потоке, поскольку переключение с одного элемента в коллекции на другой происходит довольно быстро и не приводит к зависанию пользовательского интерфейса. Просто перенесите «дорогостоящую» вычислительную работу в рабочий поток:
private async void StartParseButtonClick(object sender, EventArgs e)
{
// disable button (we are on UI thread)
var startParseButton = sender as Button;
startParseButton.Enabled = false;
try
{
// copy just in case if someone will add new item while we iterating over
var items = listView1.Items.OfType<ListViewItem>().ToList();
foreach (var item in items)
await Parse(item); // this will be invoked in worker thread
}
finally
{
// enable button finally (we are on UI thread)
startParseButton.Enabled = true;
}
}
private async Task Parse(ListViewItem item)
{
// slowwww work (we are on worker thread)
await Task.Delay(500);
}