【问题标题】:Cross thread exception using WhenAll method使用WhenAll方法的跨线程异常
【发布时间】:2021-07-14 06:36:01
【问题描述】:

我正在学习异步/等待,我创建了一个对我不起作用的小例子。我想以并行方式更改文本道具。有可能吗?

错误提示“'跨线程操作无效:控件'textBox1'从创建它的线程以外的线程访问”。

private async void DeleteAsync()
    {
        var tasks = new List<Task>();
        var list1 = new List<Control>();
        var list2 = new List<Control>();


        list1.Add(textBox1);
        list1.Add(textBox2);

        list2.Add(textBox3);
        list2.Add(textBox4);


        tasks.Add(Task.Run(() => ChangeText(list1)));
        tasks.Add(Task.Run(() => ChangeText(list2)));

        await Task.WhenAll(tasks);

        Console.Write("enddd");
    }


    private void ChangeText(List<Control> lst)
    {            
        foreach (var ctrl in lst)
        {
            ctrl.Text = "22";
        }            
    }

非常感谢!

【问题讨论】:

  • 查看标记的重复项。那里进行了广泛的讨论,以及涉及“跨线程操作无效”异常的任何其他现有问题,这将告诉您线程与 UI 对象的交互如何工作。跨度>
  • 重新开放; duplicate question 已有十多年的历史,所有投票最多的答案都推荐了非常过时的技术,例如 Invoke/BeginInvoke

标签: c# .net multithreading async-await


【解决方案1】:

我想以并行方式更改文本道具。有可能吗?

没有。但是您可以在合理的范围内对 UI 线程进行尽可能多的更改。

一种方法是using plain async/await, without using Task.Run。换句话说,使用异步而不是并行。

如果您确实需要并行性,那么更新 UI 的一种方法是使用 IProgress&lt;T&gt;/Progress&lt;T&gt; 类型来报告进度更新。像这样的:

var progress1 = new Progress<string>(update =>
{
  foreach (var ctrl in list1)
    ctrl.Text = update;
});
var progress2 = new Progress<string>(update =>
{
  foreach (var ctrl in list2)
    ctrl.Text = update;
});

tasks.Add(Task.Run(() => ChangeText(progress1)));
tasks.Add(Task.Run(() => ChangeText(progress2)));
await Task.WhenAll(tasks);

...

private void ChangeText(IProgress<string> progress)
{
  progress?.Report("22");
}

使用IProgress&lt;T&gt; 方法的一个好处是您的处理代码现在可以在没有用户界面的情况下进行测试。即,您可以为其编写单元测试。

【讨论】:

  • 嗨,斯蒂芬,感谢您的回复,我猜您试图说进度?.Report("22") ,真的吗?
【解决方案2】:

就目前而言,不需要 Async Await PatternTasks,因为您所做的只是更新 UIUI单线程

虽然假设您正在做一些 IO Bound 性质的事情,这可能会导致您的 UI 线程 停滞(如访问数据库等),但您可以执行类似的操作这个。

private async Task DoSomethingAsync()
{
    ...

    await DoSomethingAwesomeAsync(list1);
    await DoSomethingAwesomeAsync(list2);

}

...

private async Task ChangeText(List<Control> lst)
{           
    // Awesome IO bound work here

    // await CallDataBaseAsync();

    // await VisitGrandMotherAsync();

    foreach (var ctrl in lst)
    {
        ctrl.Text = "22";
    }            
}

在这种情况下,每次调用 await 时,当前的 SynchronizationContext 都会传递给编译器创建的 IAsyncStateMachine,并且继续在调用 context 上运行(即之后的所有内容await)。这意味着您的所有 UI 代码都将回发到 UI 线程(这将否定您的跨线程异常)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多