简体   繁体   English

一次异步等待一个任务

[英]async await for a single task at a time

During my job interview, I was given a task to create an asynchronous wrapper over some long running method, processing some data, but to create it so that only a single task could be running at a time.在我的求职面试中,我接到了一项任务,即在一些长时间运行的方法上创建一个异步包装器,处理一些数据,但要创建它以便一次只能运行一个任务。 I was not very familiar with async/await pattern, so I did my best and wrote some mixup between task-style and event-style, so that my wrapper was holding a task currently being executed, and exposing a public method and a public event.我对async/await模式不是很熟悉,所以我尽力了,写了一些 task-style 和 event-style 之间的混合,这样我的包装器就持有一个当前正在执行的任务,并暴露一个公共方法和一个公共事件. Method took data to process as an argument, and if there was no task running, started one, if there was a task, it enqueued the data.方法以要处理的数据为参数,如果没有正在运行的任务,则启动一个,如果有任务,则将数据入队。 Task was raising the public event upon completion, which was sending process results to subscribers and starting a new task if there is any enqueued.任务在完成后引发公共事件,该事件将过程结果发送给订阅者,并在有任何排队的情况下启动新任务。

So, as you could probably guess by that point, I failed an interview, but now that I did some research, I am trying to figure out how to properly do it (it should have also been thread-safe, but I was too busy worrying about that).所以,到那时你可能已经猜到了,我面试失败了,但现在我做了一些研究,我试图弄清楚如何正确地做到这一点(它也应该是线程安全的,但我太忙了担心那个)。 So my question is, if I have所以我的问题是,如果我有

public class SynchronousProcessor
{
    public string Process(string arg)
    {
        Thread.Sleep(1500); //Work imitation
        return someRandomString;
    }
}

public class AsynchronousWrapper
{
    SynchronousProcessor proc = new SynchronousProcessor();

    public async Task<string> ProcessAsync(string arg)
    {
        return Task.Run(() => proc.Process(arg));
    } 
}

, or something like this, how do I properly handle calls to ProcessAsync(string) if there is already a task executing? ,或类似的东西,如果已经有任务在执行,我该如何正确处理对ProcessAsync(string)调用?

Many job interview questions are asked for a purpose other than to see you write the code.许多求职面试问题的目的不是为了看你写代码。 Usually, questions are a bit vague specifically to see what clarifying questions you ask - your questions determine how well you do.通常,问题有点含糊,特别是看您提出的澄清问题 -您的问题决定了您的表现。 Writing code on a whiteboard is secondary at best.在白板上编写代码充其量是次要的。

I was given a task to create an asynchronous wrapper over some long running method, processing some data我的任务是在一些长时间运行的方法上创建一个异步包装器,处理一些数据

First question: is this long-running method asynchronous?第一个问题:这个长时间运行的方法是异步的吗? If so, then there would not be a need for Task.Run .如果是这样,则不需要Task.Run But if not...但如果不是...

Followup question: if it's not asynchronous, should it be?后续问题:如果它不是异步的,应该是吗? Ie, is it I/O-based?即,它是基于 I/O 的吗? If so, then we could invest the time to make it properly asynchronous.如果是这样,那么我们可以投入时间使其正确异步。 But if not...但如果不是...

Followup question: if we need a task wrapper (around CPU-based code or around blocking I/O code), is the environment agreeable to a wrapper?后续问题:如果我们需要一个任务包装器(围绕基于 CPU 的代码或围绕阻塞 I/O 代码),环境是否适合包装器? Ie, is this a desktop/mobile app and not code that would be used in ASP.NET?即,这是一个桌面/移动应用程序,而不是将在 ASP.NET 中使用的代码?

create it so that only a single task could be running at a time.创建它以便一次只能运行一个任务。

Clarifying questions: if a second request comes in when one is already running, does the second request "queue up"?澄清问题:如果第二个请求已经在运行,第二个请求是否“排队”? Or would it "merge" with an existing request?或者它会与现有请求“合并”吗? If merging, do they need to "key" off of the input data - or some subset of the input data?如果合并,他们是否需要“关闭”输入数据 - 或输入数据的某个子集?

Every one of these questions change how the answer is structured.这些问题中的每一个都会改变答案的结构。

exposing a public method and a public event.公开一个公共方法和一个公共事件。

This could be what threw it.这可能是扔它的原因。 Between Task<T> / IProgress<T> and Rx, events are seldom needed.Task<T> / IProgress<T>和 Rx 之间,很少需要事件。 They really only should be used if you're on a team that won't learn Rx.只有当你所在的团队不会学习 Rx 时,才应该使用它们。

Oh, and don't worry about "failing" an interview.哦,不要担心“失败”面试。 I've "failed" over 2/3 of my interviews over the course of my career.在我的职业生涯中,我有超过 2/3 的面试“失败”。 I just don't interview well.我只是不擅长面试。

As @MickyD already said, you need to know the Best Practices in Asynchronous Programming to solve such problems right way.正如@MickyD 已经说过的,您需要了解异步编程中最佳实践才能以正确的方式解决此类问题。 Your solution has a code smell as it provide async wrapper with Task.Run for a synchronous code .您的解决方案具有代码异味,因为它为同步代码提供了带有Task.Run异步包装器 As you were asked about the library development, it will be quite impacting your library consumers.当您被问及图书馆开发时,它将对您的图书馆消费者产生很大影响。

You have to understand that asynchronous isn't multithreading , as it can be done with one thread.您必须了解asynchronous不是multithreading ,因为它可以用一个线程完成。 It's like waiting for a mail - you don't hire a worker to wait by the mailbox .这就像等待邮件一样——你不会雇佣工人在邮箱旁等待

Other solutions here aren't, well, async, because break other rule for async code: do not block async action , so you should avoid the lock construct.这里的其他解决方案不是异步的,因为违反了async代码的其他规则:不要阻止 async action ,因此您应该避免使用lock构造。

So, back to your problem: if you face a task which states所以,回到你的问题:如果你面临一项任务,其中规定

only a single task could be running at a time一次只能运行一个任务

It is not about the lock ( Monitor ), it is about Semaphore(Slim) .这不是关于lockMonitor ),而是关于Semaphore(Slim) If for some reason in future you'll need to improve your code so more than one task can be executed simultaneously, you'll have to rewrite your code.如果将来出于某种原因您需要改进您的代码以便可以同时执行多个任务,您将不得不重写您的代码。 In case of Semaphore usage you'll need to change only one constant.在使用Semaphore情况下,您只需要更改一个常量。 Also it has an async wrappers for waiting methods它还有一个用于等待方法的async包装器

So your code can be like this (note that the Task.Run is removed, as it is a client responsibility to provide an awaitable):所以你的代码可以是这样的(注意Task.Run被删除了,因为提供等待是客户端的责任):

public class AsynchronousWrapper
{
    private static SemaphoreSlim _mutex = new SemaphoreSlim(1);

    public async Task<T> ProcessAsync<T>(Task<T> arg)
    {
        await _mutex.WaitAsync().ConfigureAwait(false);

        try
        {
            return await arg;
        }
        finally
        {
            _mutex.Release();
        }
    }
}

It depends on how fancy you want to get.这取决于您想要获得的花哨程度。 One simple way is to store a task, and chain the subsequent tasks (with a bit of synchronization):一种简单的方法是存储一个任务,并将后续任务链接起来(有点同步):

public class AsynchronousWrapper
{
    private Task previousTask = Task.CompletedTask;     
    private SynchronousProcessor proc = new SynchronousProcessor();

    public Task<string> ProcessAsync(string arg)
    {
        lock (proc)
        {
            var task = previousTask.ContinueWith(_ => proc.Process(arg));

            previousTask = task;

            return task;
        }           
    } 
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM