可以从多个线程等待相同的任务 - 等待线程安全吗?

Posted

技术标签:

【中文标题】可以从多个线程等待相同的任务 - 等待线程安全吗?【英文标题】:Is it ok to await the same task from multiple threads - is await thread safe? 【发布时间】:2013-05-29 17:32:02 【问题描述】:

等待线程安全吗?似乎 Task 类是线程安全的,所以我想等待它也是线程安全的,但我没有在任何地方找到确认。线程安全也是自定义等待器的要求 - 我的意思是 IsCompleted、GetAwaiter 等方法? IE。如果这些方法不是线程安全的,那么等待是线程安全的吗?但是,我预计不会很快需要自定义等待器。

用户场景示例:假设我有一个异步返回结果的后台任务,然后从多个线程中使用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace scratch1

    class Foo
    
        Task<int> _task;

        public Foo()
        
            _task = Task.Run(async () =>  await Task.Delay(5000); return 5; );
        

        // Called from multiple threads
        public async Task<int> Bar(int i)
        
            return (await _task) + i;
        
    

    class Program
    
        public static void Main(string[] args)
        
            Foo foo = new Foo();

            List<Task> tasks = new List<Task>();
            foreach (var i in Enumerable.Range(0, 100))
            
                tasks.Add(Task.Run(async () =>  Console.WriteLine(await foo.Bar(i)); )); 
            

            Task.WaitAll(tasks.ToArray());
        
    

【问题讨论】:

【参考方案1】:

正如你所提到的,await 足够薄,一开始就看不到线。

await 相关的代码(状态机中编译器生成的代码,以及BCL 中的支持类)一次只能在一个线程上运行。 (从等待返回时可以切换到不同的线程,但之前的运行已经完成)

实际的线程安全取决于您等待的对象。 它必须有一种线程安全的方式来添加回调(或者await一次两次可能会中断)。

此外,它必须只调用一次回调,否则可能会破坏状态机。 (特别是,如果它同时在两个线程上运行它的回调,事情会变得非常糟糕)

Task 是线程安全的。

【讨论】:

您的第一个陈述可能会误导人们认为await 从根本上来说是不安全的......而我认为您的意思是它既不是本质上的安全也不是不安全的——它只是代表正在等待的任何东西,如果那是@ 987654326@,你没事。 当然,谢谢。虽然“将只在一个线程上运行”可能应该是“每次只在一个线程上运行,每次方法调用(对应于状态机的单个实例)”。它当然可以在await 点的线程之间跳来跳去,但它不会在多个线程上同时运行。 (至少不会,除非你严重滥用它......这总是很有趣。)

以上是关于可以从多个线程等待相同的任务 - 等待线程安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

等待唤醒机制

等待与唤醒案例

等待与唤醒机制(线程之间的通信)

java之等待唤醒机制(线程之间的通信)

chapter19等待与唤醒案例线程池Lambda表达式

JAVASE-DAY14