从交叉线程获取UI线程的值

时间:2019-03-12 02:45:50

标签: c# multithreading

我试图在我的程序中添加线程,以便在执行“昂贵的”计算工作时不会冻结整个主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中有更好/更有效的方法?

我可能只是缺乏对异步/任务的基本了解,但我想我忽略了一些重要的事情。

2 个答案:

答案 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.ListViewItemCollectionListViewItem是“线程仿射”。这意味着只能在UI线程上访问它们。

要进行后台工作,应仅传递非线程仿射对象。例如List<string>,而不是ListViewItemCollectionList<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>一起使用。