可以在 ThreadStart 方法中使用“async”吗?

问题描述 投票:0回答:2

我有一个 Windows 服务,它使用 Thread 和 SemaphoreSlim 每 60 秒执行一些“工作”。

class Daemon
{
    private SemaphoreSlim _semaphore;
    private Thread _thread;

    public void Stop()
    {
        _semaphore.Release();
        _thread.Join();
    }

    public void Start()
    {
        _semaphore = new SemaphoreSlim(0);
        _thread = new Thread(DoWork);
        _thread.Start();
    }

    private void DoWork()
    {
        while (true)
        {
            // Do some work here

            // Wait for 60 seconds, or exit if the Semaphore is released
            if (_semaphore.Wait(60 * 1000))                
            {
                return;
            }
        }
    }
}

我想从

DoWork
调用异步方法。为了使用
await
关键字,我必须将
async
添加到
DoWork
:

private async void DoWork()
  1. 有什么理由不这样做吗?
  2. 如果 DoWork 已经在专用线程内运行,它实际上能够异步运行吗?
c# async-await
2个回答
20
投票

可以这样做,但这不是一个好主意。一旦第一个

await
命中且未同步完成,其余工作将在 continuation 上完成,而不是在您启动的线程 (
_thread
) 上完成;您启动的线程将在第一个这样的
await
处终止。无法保证延续会返回到原始线程,并且在您的场景中,它不能 - 该线程现在已结束。这意味着:

  1. _thread
    无意义,不代表运行状态;因此,带有
    Stop()
    _thread.Join();
    方法不会执行您期望的操作
  2. 您创建了一个线程(分配线程是昂贵的,特别是因为堆栈的大小)只是让它几乎立即退出

这两个问题都可以通过使用

Task.Run
启动此类操作来避免。


0
投票

async
ThreadStart
方法一起使用可以吗?

不,绝对不是。

Thread
构造函数不理解
async
委托。
async void
出于特定原因,C# 中允许使用方法/委托,以使异步事件处理程序成为可能。除此之外,很少有情况下使用
async void
方法是有意义的,而
Thread
构造函数不是其中之一。

有什么理由不这样做吗?

是的,至少两个。

  1. 浪费资源。当到达不完整等待的第一个

    Thread
    时,新的
    await
    将立即终止。其余代码将在其他线程上运行,通常(但不一定
    ThreadPool
    线程。实例化一个新的
    Thread
    是一个相对昂贵的操作。对于创建新线程的工作量没有硬性规定,但我想说任何低于 500 毫秒的时间都是不合理的。

  2. 表示失败。新创建的

    Thread
    实例并不代表异步操作完成。仅代表
    async void
    行动的启动。没有正式的方法可以知道
    async void
    操作何时完成(除了 extreme hacks)。因此,持有
    Thread
    实例的引用充其量是无用的,最坏的情况是会产生误导。

如果

DoWork
已经在专用线程内运行,它实际上能够异步运行吗?

通常异步操作在无线程上运行。它们主要使用CPU以外的硬件,如磁盘驱动器或网卡。通常,他们在非常短的时间跨度上使用 CPU,线程是由他们调用的原始(内置)异步方法的实现选择的(如

HttpClient.GetAsync
)。这些线程几乎总是
ThreadPool
线程。所以,不,
DoWork
不会在专用线程上运行。大部分情况下,它根本不会在任何线程上运行,并且会短暂地在
ThreadPool
线程上运行。

该怎么办:

您可以使用

async void
 启动 
ThreadPool
操作:

ThreadPool.QueueUserWorkItem(_ => DoWork()); // DoWork is an async void method

这样您就可以避免支付创建新

Thread
的成本,并且还可以避免前面讨论的表示失败。这是一种在不阻塞当前线程的情况下以“fire-and-crash”方式启动操作的诚实方法。如果 DoWork 抛出异常,进程将会崩溃。
或者,您可以将 

DoWork

的签名更改为

async Task
,然后使用
ThreadPool
 将其卸载到 Task.Run
Task task = Task.Run(async () => await DoWork()); // DoWork is an async Task method

通过这种方式,您将获得 
DoWork

操作的良好表示。

task
变量代表操作完成,任何异常都会存储在这个
Task
中。如果出现错误,该过程不会崩溃,但在某些时候您应该 
await
.Wait()
task
,以避免让它变成
即发即忘

© www.soinside.com 2019 - 2024. All rights reserved.