c# - 基于异步/等待的 Windows 服务中的同步 I/O

标签 c# .net asynchronous async-await

假设我有一个 Windows 服务,它正在做一些工作,然后休眠一小段时间,一遍又一遍地永远(直到服务关闭)。所以在服务的 OnStart 中,我可以启动一个线程,其入口点类似于:

private void WorkerThreadFunc()
{
    while (!shuttingDown)
    {
        DoSomething();
        Thread.Sleep(10);
    }
}

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

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

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

问题 #1:呃……对吧?我是 async/await 的新手,但仍在努力了解它。

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

问题 #2:那不好吗?我不应该在基于异步/等待的程序中的任务中执行同步 I/O?因为它在 I/O 发生时占用线程池中的线程,而线程池中的线程是高度有限的资源?请注意,我可能有数十个这样的 Worker 同时连接到不同的硬件。

我不确定我是否理解正确 - 我从像 Stephen Cleary 的“Task.Run Etiquette Examples: Don't Use Task.Run for the Wrong Thing”这样的文章中得到这样的想法,但这特别是关于在 Task.Run 中进行阻塞工作是不好的。我不确定如果我只是直接执行它是否也很糟糕,如上面的“private async Task Work()”示例?

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

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

问题 #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;
}

但这似乎只是将问题推得更远,而不是解决问题。 WorkAsync() 在调用 WrappedDoSomething() 时仍会阻塞,并且只有在 WrappedDoSomething() 已经完成阻塞工作后才会进入“等待”状态。对吧?

鉴于(如果我理解正确的话)在一般情况下,应该允许 async/await 在程序中上下“传播”,这是否意味着如果我需要使用这样的库,我基本上不应该让程序基于异步/等待?我应该回到 Thread/WorkerThreadFunc/Thread.Sleep 世界吗?

如果基于 async/await 的程序已经存在,正在做其他事情,但现在需要添加使用此类库的附加功能怎么办?这是否意味着基于 async/await 的程序应该重写为基于 Thread/etc. 的程序?

最佳答案

Actually there might be several such threads, and other threads too, all started in OnStart and shut down/joined in OnStop.

附带说明一下,拥有一个启动/加入所有其他线程的“主”线程通常更简单。然后OnStart/OnStop只是处理主线程。

If I want to instead do this sort of thing in an async/await based Windows Service, it seems like I could have OnStart create cancelable tasks but not await (or wait) on them, and have OnStop cancel those tasks and then Task.WhenAll().Wait() on them.

这是一个完全可以接受的方法。

If I understand correctly, the equivalent of the "WorkerThreadFunc" shown above might be something like:

大概是想把CancellationToken传下去;同步代码也可以使用取消:

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

Question #1: Uh... right? I am new to async/await and still trying to get my head around it.

这不是错误,但它只会在 Win32 服务上为您节省一个线程,这对您没有多大帮助。

Question #2: That is bad? I shouldn't be doing synchronous I/O within a Task in an async/await-based program? Because it ties up a thread from the thread pool while the I/O is happening, and threads from the thread pool are a highly limited resource? Please note that I might have dozens of such Workers going simultaneously to different pieces of hardware.

几十个线程不是很多。通常,异步 I/O 更好,因为它根本不使用任何线程,但在这种情况下,您在桌面上,因此线程不是高度有限的资源。 async 对 UI 应用(其中 UI 线程很特殊,需要释放)和需要扩展的 ASP.NET 应用(其中线程池限制了可扩展性)最有利。

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

I am getting the idea that it's bad from articles like Stephen Cleary's "Task.Run Etiquette Examples: Don't Use Task.Run for the Wrong Thing", but that's specifically about it being bad to do blocking work within Task.Run.

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

Assuming that's bad too, then if I understand correctly I should instead utilize the nonblocking version of DoSomething (creating a nonblocking version of it if it doesn't already exist)

异步更好(在资源利用率方面——即使用更少的线程),所以如果你想/需要减少线程数,你应该使用异步。

Question #3: But... what if DoSomething is from a third party library, which I must use and cannot alter, and that library doesn't expose a nonblocking version of DoSomething? It's just a black box set in stone that at some point does a blocking write to a piece of hardware.

那就直接调用吧

Maybe I wrap it and use TaskCompletionSource?

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

But that seems like it's just pushing the issue down a bit further rather than resolving it. WorkAsync() will still block when it calls WrappedDoSomething(), and only get to the "await" for that after WrappedDoSomething() has already completed the blocking work. Right?

是的。

Given that (if I understand correctly) in the general case async/await should be allowed to "spread" all the way up and down in a program, would this mean that if I need to use such a library, I essentially should not make the program async/await-based? I should go back to the Thread/WorkerThreadFunc/Thread.Sleep world?

假设您已经有一个阻塞的 Win32 服务,保持它原样可能没问题。如果您正在编写一个新的,我个人会将其设为 async 以减少线程并允许异步 API,但您必须以任何一种方式进行。一般来说,我更喜欢 Task 而不是 Thread,因为从 Task 中获取结果(包括异常)要容易得多。

async all the way”规则只有一种方式。也就是说,一旦你调用了一个async方法,那么它的调用者应该是async,它的调用者应该是async,等等。它确实< em>不 意味着async 方法调用 的每个方法都必须是async

因此,拥有异步 Win32 服务的一个很好的理由是,如果您需要使用仅异步的 API。这会导致您的 DoSomething 方法变为 async DoSomethingAsync

What if an async/await-based program already exists, doing other things, but now additional functionality that uses such a library needs to be added to it? Does that mean that the async/await-based program should be rewritten as a Thread/etc.-based program?

没有。你总是可以阻止 async 方法。有了适当的文档,当你在一年后重用/维护这段代码时,你不会对过去的自己发誓。 :)

关于c# - 基于异步/等待的 Windows 服务中的同步 I/O,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37000403/

相关文章:

c# - ResolveEventHandler只调用一次,成功或失败

C#反编译

c# - 需要通过 Union() 中的匿名类型显式转换

java - Spring 中的异步 Controller 和方法

c# - 使用 SQL 更新 250k 行的更快方法

c# - 如何知道谁访问了页面?

c# - XAML Treeview,如何水平而不是垂直显示节点

c++ - 跨平台 C++ 工具链

c# - 为什么即使处理程序在 WPF 应用程序生命周期中是异步无效的,Application.Exit 事件仍然有效?

javascript - 使用回调或 promise 使异步代码像 nodejs 中的同步一样工作