我试图在我的程序中添加线程,以便在执行“昂贵的”计算工作时不会冻结整个主UI线程。
当前,当按下按钮时,我的程序会运行一个异步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中有更好/更有效的方法?
我可能只是缺乏对异步/任务的基本了解,但我想我忽略了一些重要的事情。
答案 0 :(得分:1)
您不必遍历工作线程中的项目,因为从集合中的一个项目切换到另一个项目非常快,而且不会冻结UI。只需将“昂贵”的计算工作移至工作线程即可:
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);
}
答案 1 :(得分:1)
UI元素,包括ListView.ListViewItemCollection
和ListViewItem
是“线程仿射”。这意味着只能在UI线程上访问它们。
要进行后台工作,应仅传递非线程仿射对象。例如List<string>
,而不是ListViewItemCollection
或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>
与Progress<T>
一起使用。