基于异步/等待的Windows服务中的同步I/O [英] Synchronous I/O within an async/await-based Windows Service

查看:84
本文介绍了基于异步/等待的Windows服务中的同步I/O的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

比方说,我有一个Windows服务,它正在做一些工作,然后短暂地睡眠一小段时间(直到该服务被关闭).因此,在服务的OnStart中,我可以启动一个线程,其入口点如下:

Let's say I have a Windows Service which is doing some bit of work, then sleeping for a short amount of time, over and over forever (until the service is shut down). So in the service's OnStart, I could start up a thread whose entry point is something like:

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

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

And in the service's OnStop, I somehow set that shuttingDown flag and then join the thread. Actually there might be several such threads, and other threads too, all started in OnStart and shut down/joined in OnStop.

如果我想改为在基于异步/等待的Windows服务中执行这种操作,似乎我可以让OnStart创建可取消的任务,但不等待(或等待)它们,然后让OnStop取消这些任务,然后Task.WhenAll().Wait()在它们上面.如果我理解正确,那么上面显示的"WorkerThreadFunc"等效项可能类似于:

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:

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

问题1:嗯...对吗?我是异步/等待的新手,但仍然想尽办法解决问题.

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

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

Assuming that's right, now let's say that DoSomething() call is (or includes) a synchronous write I/O to some piece of hardware. If I'm understanding correctly:

问题2:不好吗?我不应该在基于async/await的程序中的Task中执行同步I/O吗?因为它在I/O发生时绑定了线程池中的线程,并且线程池中的线程是资源非常有限的?请注意,我可能有数十名这样的工作人员同时使用不同的硬件.

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.

我不确定我是否正确理解这一点-我从斯蒂芬·克雷里(Stephen Cleary)的"

I am not sure I'm understanding that correctly - 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. I'm not sure if it's also bad if I'm just doing it directly, as in the "private async Task Work()" example above?

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

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), and then:

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的非阻塞版本,该怎么办?只是一个黑石头固定的盒子,在某个时候,它会阻塞对一块硬件的写入.

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.

也许我将其包装并使用TaskCompletionSource?像这样:

Maybe I wrap it and use TaskCompletionSource? Something like:

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()已经完成阻塞工作之后才进入等待状态.对吧?

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?

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

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?

如果已经存在一个基于异步/等待的程序,该怎么办,但现在需要添加使用此类库的其他功能,该怎么办?这是否意味着基于异步/等待的程序应该重写为基于线程/等的程序?

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?

推荐答案

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

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

另一方面,通常只有一个主"线程来启动/加入所有其他线程,这通常会更简单.然后OnStart/OnStop只处理主线程.

On a side note, it's usually simpler to have a single "master" thread that will start/join all the others. Then OnStart/OnStop just deals with the master thread.

如果我想改为在基于异步/等待的Windows服务中执行这种操作,似乎我可以让OnStart创建可取消的任务,但不等待(或等待)它们,然后让OnStop取消这些任务,然后Task.WhenAll().Wait()上.

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.

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

That's a perfectly acceptable approach.

如果我理解正确,那么上面显示的"WorkerThreadFunc"等效项可能类似于:

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

可能想将CancellationToken传递给其他人;取消也可以由同步代码使用:

Probably want to pass the CancellationToken down; cancellation can be used by synchronous code, too:

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

问题1:嗯...对吗?我是异步/等待的新手,但仍然想尽办法解决问题.

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

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

It's not wrong, but it only saves you one thread on a Win32 service, which doesn't do much for you.

问题2:不好吗?我不应该在基于async/await的程序中的Task中执行同步I/O吗?因为它在I/O发生时绑定了线程池中的线程,并且线程池中的线程是资源非常有限的?请注意,我可能有数十名这样的工作人员同时使用不同的硬件.

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应用程序(线程池限制可伸缩性)上最有利.

Dozens of threads are not a lot. Generally, asynchronous I/O is better because it doesn't use any threads at all, but in this case you're on the desktop, so threads are not a highly limited resource. async is most beneficial on UI apps (where the UI thread is special and needs to be freed), and ASP.NET apps that need to scale (where the thread pool limits scalability).

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

Bottom line: calling a blocking method from an asynchronous method is not bad but it's not the best, either. If there is an asynchronous method, call that instead. But if there isn't, then just keep the blocking call and document it in the XML comments for that method (because an asynchronous method blocking is rather surprising behavior).

我从斯蒂芬·克莱里(Stephen Cleary)的"Task.Run礼节示例:不要为错误的事情使用Task.Run礼节"之类的文章中意识到,这样做很不好,但这特别是因为在Task中进行阻塞工作是不好的.运行.

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包装同步方法并假装它们是异步的.这是一个常见的错误;它所做的只是将一个线程池线程换为另一个线程池.

Yes, that is specifically about using Task.Run to wrap synchronous methods and pretend they're asynchronous. It's a common mistake; all it does is trade one thread pool thread for another.

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

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)

异步性更好(就资源利用率而言,即使用的线程数较少),因此,如果希望/需要减少线程数,则应使用异步.

Asynchronous is better (in terms of resource utilization - that is, fewer threads used), so if you want/need to reduce the number of threads, you should use async.

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

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.

然后直接调用它即可.

也许我将其包装并使用TaskCompletionSource?

Maybe I wrap it and use TaskCompletionSource?

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

No, that doesn't do anything useful. That just calls it synchronously and then returns an already-completed task.

但是,这似乎只是在进一步推迟解决问题,而不是解决问题.当WorkAsync()调用WrappedDoSomething()时,它仍然会阻塞,并且只有在WrappedDoSomething()已经完成阻塞工作之后才进入等待状态.对吧?

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?

是的.

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

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获得结果(包括异常)要容易得多.

Assuming you already have a blocking Win32 service, it's probably fine to just keep it as it is. If you are writing a new one, personally I would make it async to reduce threads and allow asynchronous APIs, but you don't have to do it either way. I prefer Tasks over Threads in general, since it's much easier to get results from Tasks (including exceptions).

"async一直"规则仅是一种方法.也就是说,一旦调用async方法,则其调用方应为async,其调用方应为async,依此类推. 通过async方法必须是async.

The "async all the way" rule only goes one way. That is, once you call an async method, then its caller should be async, and its caller should be async, etc. It does not mean that every method called by an async method must be async.

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

So, one good reason to have an async Win32 service would be if there's an async-only API you need to consume. That would cause your DoSomething method to become 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方法.有了适当的文档,因此当您一年后重用/维护此代码时,您不会对自己的过去发誓. :)

No. You can always just block from an async method. With proper documentation so when you are reusing/maintaining this code a year from now, you don't swear at your past self. :)

这篇关于基于异步/等待的Windows服务中的同步I/O的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆