我有一个 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()
你可以这样做,但这不是一个好主意。一旦第一个
await
命中且未同步完成,其余工作将在 continuation 上完成,而不是在您启动的线程 (_thread
) 上完成;您启动的线程将在第一个这样的await
处终止。无法保证延续会返回到原始线程,并且在您的场景中,它不能 - 该线程现在已结束。这意味着:
_thread
无意义,不代表运行状态;因此,带有 Stop()
的 _thread.Join();
方法不会执行您期望的操作这两个问题都可以通过使用
Task.Run
启动此类操作来避免。
将
与async
方法一起使用可以吗?ThreadStart
不,绝对不是。
Thread
构造函数不理解 async
委托。 async void
出于特定原因,C# 中允许使用方法/委托,以使异步事件处理程序成为可能。除此之外,很少有情况下使用 async void
方法是有意义的,而 Thread
构造函数不是其中之一。
有什么理由不这样做吗?
是的,至少两个。
浪费资源。当到达不完整等待的第一个
Thread
时,新的 await
将立即终止。其余代码将在其他线程上运行,通常(但不一定)ThreadPool
线程。实例化一个新的Thread
是一个相对昂贵的操作。对于创建新线程的工作量没有硬性规定,但我想说任何低于 500 毫秒的时间都是不合理的。
表示失败。新创建的
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
,以避免让它变成 即发即忘。