基于异步/等待的Windows服务中的同步I/O.

Bob*_*man 9 .net c# asynchronous async-await

假设我有一个Windows服务,它正在做一些工作,然后在很长一段时间内一直睡眠,一直到来(直到服务关闭).所以在服务的OnStart中,我可以启动一个线程,其入口点如下:

private void WorkerThreadFunc()
{
    while (!shuttingDown)
    {
        DoSomething();
        Thread.Sleep(10);
    }
}
Run Code Online (Sandbox Code Playgroud)

在服务的OnStop中,我以某种方式设置了shuttingDown标志然后加入线程.实际上可能有几个这样的线程,以及其他线程,都在OnStart中启动并在OnStop中关闭/加入.

如果我想在基于async/await的Windows服务中执行此类操作,似乎我可以让OnStart创建可取消的任务但不等待(或等待)它们,并让OnStop取消这些任务然后Task.WhenAll ().等等().如果我理解正确,上面显示的"WorkerThreadFunc"的等价物可能是这样的:

private async Task WorkAsync(CancellationToken cancel)
{
    while (true)
    {
        cancel.ThrowIfCancellationRequested();
        DoSomething();
        await Task.Delay(10, cancel).ConfigureAwait(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题1:呃......对吗?我是异步/等待的新手,仍然试图了解它.

假设这是正确的,现在让我们说DoSomething()调用是(或包括)某些硬件的同步写入I/O. 如果我理解正确:

问题2:那不好?我不应该在基于async/await的程序中的Task中进行同步I/O操作?因为它在I/O发生时从线程池中占用一个线程,而线程池中的线程是一个非常有限的资源?请注意,我可能会有数十名此类工作人员同时使用不同的硬件.

我不确定我是否理解正确 - 我认为这与Stephen Cleary的文章" Task.Run Etiquette例子:不要使用Task.Run错误的东西 "有关,但这是特别关于它的存在在Task.Run中做阻塞工作很糟糕.我不确定如果我只是直接这样做也不好,就像上面的"私人异步任务工作()"例子一样?

假设这也很糟糕,那么如果我理解正确,我应该使用DoSomething的非阻塞版本(如果它尚不存在则创建非阻塞版本),然后:

private async Task WorkAsync(CancellationToken cancel)
{
    while (true)
    {
        cancel.ThrowIfCancellationRequested();
        await DoSomethingAsync(cancel).ConfigureAwait(false);
        await Task.Delay(10, cancel).ConfigureAwait(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题3:但是......如果DoSomething来自第三方库,我必须使用并且不能更改,并且该库不会公开DoSomething的非阻塞版本,该怎么办?它只是一个黑色的盒子,在某些时候阻止写入一块硬件.

也许我包装它并使用TaskCompletionSource?就像是:

private async Task WorkAsync(CancellationToken cancel)
{
    while (true)
    {
        cancel.ThrowIfCancellationRequested();
        await WrappedDoSomething().ConfigureAwait(false);
        await Task.Delay(10, cancel).ConfigureAwait(false);
    }
}

private Task WrappedDoSomething()
{
    var tcs = new TaskCompletionSource<object>();
    DoSomething();
    tcs.SetResult(null);
    return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)

但这似乎只是将问题进一步推进而不是解决问题.WorkAsync()在调用WrappedDoSomething()时仍然会阻塞,并且在WrappedDoSomething()已经完成阻塞工作后才会到达"await".对?

鉴于(如果我理解正确)在一般情况下async/await应该被允许在程序中一直"扩散",这是否意味着如果我需要使用这样的库,我基本上不应该程序async/await-based?我应该回到Thread/WorkerThreadFunc/Thread.Sleep世界?

如果基于异步/等待的程序已经存在,做其他事情,但是现在需要添加使用这种库的其他功能呢?这是否意味着应该将基于异步/等待的程序重写为基于线程/等等的程序?

Ste*_*ary 11

实际上可能有几个这样的线程,以及其他线程,都在OnStart中启动并在OnStop中关闭/加入.

另外,一个"主"线程通常更简单,它将启动/加入所有其他线程.然后OnStart/ OnStop只处理主线程.

如果我想在基于async/await的Windows服务中执行此类操作,似乎我可以让OnStart创建可取消的任务但不等待(或等待)它们,并让OnStop取消这些任务然后Task.WhenAll ().等等().

这是一种完全可以接受的方法.

如果我理解正确,上面显示的"WorkerThreadFunc"的等价物可能是这样的:

可能想要传递CancellationToken下来; 取消也可以由同步代码使用:

private async Task WorkAsync(CancellationToken cancel)
{
  while (true)
  {
    DoSomething(cancel);
    await Task.Delay(10, cancel).ConfigureAwait(false);
  }
}
Run Code Online (Sandbox Code Playgroud)

问题1:呃......对吗?我是异步/等待的新手,仍然试图了解它.

这没有,但它只会在Win32服务上为您节省一个线程,这对您没有太大作用.

问题2:那不好?我不应该在基于async/await的程序中的Task中进行同步I/O操作?因为它在I/O发生时从线程池中占用一个线程,而线程池中的线程是一个非常有限的资源?请注意,我可能会有数十名此类工作人员同时使用不同的硬件.

几十个线程并不是很多.通常,异步I/O更好,因为它根本不使用任何线程,但在这种情况下,您在桌面上,因此线程不是非常有限的资源.async最有利于UI应用程序(UI线程特殊且需要释放),以及需要扩展的ASP.NET应用程序(线程池限制可伸缩性).

底线:从异步方法调用阻塞方法并不错,但它也不是最好的.如果有异步方法,请调用它.但是如果没有,那么只需保留阻塞调用并将其记录在该方法的XML注释中(因为异步方法阻塞是相当令人惊讶的行为).

我认为斯蒂芬克莱里的文章"任务.运行礼仪示例:不要使用Task.Run来处理错误的事情"这样的内容很糟糕,但是具体来说,在Task.Run中阻止工作是不好的.

是的,这是专门Task.Run用于包装同步方法并假装它们是异步的.这是一个常见的错误; 它只是将一个线程池线程换成另一个线程池线程.

假设这也很糟糕,那么如果我理解正确,我应该使用DoSomething的非阻塞版本(如果它尚不存在则创建非阻塞版本)

异步更好(在资源利用率方面 - 即使用的线程更少),因此如果您希望/需要减少线程数,则应使用async.

问题3:但是......如果DoSomething来自第三方库,我必须使用并且不能更改,并且该库不会公开DoSomething的非阻塞版本,该怎么办?它只是一个黑色的盒子,在某些时候阻止写入一块硬件.

然后直接调用它.

也许我包装它并使用TaskCompletionSource?

不,这没有任何用处.它只是同步调用它然后返回一个已经完成的任务.

但这似乎只是将问题进一步推进而不是解决问题.WorkAsync()在调用WrappedDoSomething()时仍然会阻塞,并且在WrappedDoSomething()已经完成阻塞工作后才会到达"await".对?

对.

鉴于(如果我理解正确)在一般情况下async/await应该被允许在程序中一直"扩散",这是否意味着如果我需要使用这样的库,我基本上不应该程序async/await-based?我应该回到Thread/WorkerThreadFunc/Thread.Sleep世界?

假设您已经拥有阻止的Win32服务,那么保持它原样可能很好.如果你正在写一个新的,我个人将使它async减少线程和允许异步API,但是,你不要做任何一种方式.我更喜欢Tasks over Threads,因为从Tasks(包括例外)获得结果要容易得多.

" async一直"规则只有一种方式.也就是说,一旦你调用一个async方法,那么它的调用者应该是async和它的调用者应该async等它并不意味着每一个方法async方法必须是async.

因此,如果您需要使用仅支持异步的API,那么拥有异步Win32服务的一个很好的理由就是如此.这会导致你的DoSomething方法成为async DoSomethingAsync.

如果基于异步/等待的程序已经存在,做其他事情,但是现在需要添加使用这种库的其他功能呢?这是否意味着应该将基于异步/等待的程序重写为基于线程/等等的程序?

不可以.你总是可以阻止一种async方法.有了适当的文档,所以当你从现在开始重用/维护这段代码时,你不要发誓过去的自我.:)

  • 这是一个非常明智的答案。并非所有事物都应该或应该是异步的,原因是异步导致开发人员正常工作,并增加了每单位代码的错误率。这是主要的对策。如果您不同步,则应该有具体的好处。 (2认同)