[英]async await for a single task at a time
在我的求职面试中,我接到了一项任务,即在一些长时间运行的方法上创建一个异步包装器,处理一些数据,但要创建它以便一次只能运行一个任务。 我对async/await
模式不是很熟悉,所以我尽力了,写了一些 task-style 和 event-style 之间的混合,这样我的包装器就持有一个当前正在执行的任务,并暴露一个公共方法和一个公共事件. 方法以要处理的数据为参数,如果没有正在运行的任务,则启动一个,如果有任务,则将数据入队。 任务在完成后引发公共事件,该事件将过程结果发送给订阅者,并在有任何排队的情况下启动新任务。
所以,到那时你可能已经猜到了,我面试失败了,但现在我做了一些研究,我试图弄清楚如何正确地做到这一点(它也应该是线程安全的,但我太忙了担心那个)。 所以我的问题是,如果我有
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));
}
}
,或类似的东西,如果已经有任务在执行,我该如何正确处理对ProcessAsync(string)
调用?
许多求职面试问题的目的不是为了看你写代码。 通常,问题有点含糊,特别是看您提出的澄清问题 -您的问题决定了您的表现。 在白板上编写代码充其量是次要的。
我的任务是在一些长时间运行的方法上创建一个异步包装器,处理一些数据
第一个问题:这个长时间运行的方法是异步的吗? 如果是这样,则不需要Task.Run
。 但如果不是...
后续问题:如果它不是异步的,应该是吗? 即,它是基于 I/O 的吗? 如果是这样,那么我们可以投入时间使其正确异步。 但如果不是...
后续问题:如果我们需要一个任务包装器(围绕基于 CPU 的代码或围绕阻塞 I/O 代码),环境是否适合包装器? 即,这是一个桌面/移动应用程序,而不是将在 ASP.NET 中使用的代码?
创建它以便一次只能运行一个任务。
澄清问题:如果第二个请求已经在运行,第二个请求是否“排队”? 或者它会与现有请求“合并”吗? 如果合并,他们是否需要“关闭”输入数据 - 或输入数据的某个子集?
这些问题中的每一个都会改变答案的结构。
公开一个公共方法和一个公共事件。
这可能是扔它的原因。 在Task<T>
/ IProgress<T>
和 Rx 之间,很少需要事件。 只有当你所在的团队不会学习 Rx 时,才应该使用它们。
哦,不要担心“失败”面试。 在我的职业生涯中,我有超过 2/3 的面试“失败”。 我只是不擅长面试。
正如@MickyD 已经说过的,您需要了解异步编程中的最佳实践才能以正确的方式解决此类问题。 您的解决方案具有代码异味,因为它为同步代码提供了带有Task.Run
异步包装器。 当您被问及图书馆开发时,它将对您的图书馆消费者产生很大影响。
您必须了解asynchronous
不是multithreading
,因为它可以用一个线程完成。 这就像等待邮件一样——你不会雇佣工人在邮箱旁等待。
这里的其他解决方案不是异步的,因为违反了async
代码的其他规则:不要阻止 async action ,因此您应该避免使用lock
构造。
所以,回到你的问题:如果你面临一项任务,其中规定
一次只能运行一个任务
这不是关于lock
( Monitor
),而是关于Semaphore(Slim)
。 如果将来出于某种原因您需要改进您的代码以便可以同时执行多个任务,您将不得不重写您的代码。 在使用Semaphore
情况下,您只需要更改一个常量。 它还有一个用于等待方法的async
包装器
所以你的代码可以是这样的(注意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();
}
}
}
这取决于您想要获得的花哨程度。 一种简单的方法是存储一个任务,并将后续任务链接起来(有点同步):
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.