通过使同步方法异步

时间:2018-06-30 21:00:02

标签: c# asynchronous async-await remoting

我有一堆都是同步的远程调用(第三方库)。它们中的大多数会花费很多时间,因此我每秒不能使用5到10次以上。这太慢了,因为我需要每两分钟至少调用3000次,如果服务停止了一段时间则要调用更多次。客户端上实际上没有CPU工作。它获取数据,检查一些简单条件,并进行另一个必须等​​待的调用。

使它们异步(以异步方式调用它们-我想我需要一些异步包装器)的最佳方法是什么,以便我可以同时发出更多请求?目前,它受线程数(四个)的限制。

我当时正在考虑用Task.Run来调用它们,但是我读过的每篇文章都说这是CPU限制的工作,并且它使用线程池线程。如果我正确地获得它,则使用这种方法将无法打破线程限制,对吗?那么哪种方法最适合这里呢?

Task.FromResult呢?我可以异步地等待比线程数更多的此类方法吗?

public async Task<Data> GetDataTakingLotsOfTime(object id)
{
    var data = remoting.GetData(id);
    return await Task.FromResult(data);
}

2 个答案:

答案 0 :(得分:1)

  

我当时正在考虑使用Task.Run调用它们,但是我读过的每篇文章都说这是CPU限制的工作,并且它使用线程池线程。

是的,但是当您使用同步API时,Task.Run()可能是您的小恶魔,尤其是在客户端上。

您当前的GetDataTakingLotsOfTime()版本实际上不是异步的。 FromResult()仅有助于抑制警告。

  

那Task.FromResult呢?我可以异步地等待比线程数更多的此类方法吗?

不清楚您的“线程数”的想法是从哪里来的,但是可以,启动Task方法并稍后等待它实际上是在ThreadPool上运行它。但是Task.Run在这方面更加清晰。

请注意,这不依赖于方法的async修饰符-异步是一种实现细节,调用方只关心它返回一个Task。

  

当前,它受线程数(四个)的限制。

这需要一些解释。我不明白

答案 1 :(得分:1)

您正在执行远程调用,并且您的线程需要空闲地等待远程调用的结果。在此等待期间,您的线程可以做一些有用的事情,例如执行其他远程调用。

当线程空闲地等待其他进程完成(例如写入磁盘,查询数据库或从Internet获取信息)的时间通常是在非异步函数旁边看到异步函数的情况: WriteWriteAsyncSendSendAsync

如果在同步呼叫的最深层,您可以访问该呼叫的异步版本,那么您的生活将会很轻松。 las,看来您没有这样的异步版本。

您使用Task.Run提出的解决方案的缺点是启动新线程(或从线程池运行一个线程)的开销。

您可以通过创建Workshop对象来减少此开销。在车间中,一个专用线程(一个工人)或几个专用线程在一个输入点等待命令执行某项操作。线程执行任务并将结果发布到输出点。

研讨会的用户有一个访问点(前台吗?),他们在其中发布请求以执行某项操作,并等待结果。

为此,我使用了System.Threading.Tasks.Dataflow.BufferBlock。安装Nuget软件包TPL Dataflow。

您可以将自己的工作室专用于仅接受GetDataTakingLotsOfTime的工作;我使研讨会变得通用:我接受实现IWork接口的所有工作:

interface IWork
{
    void DoWork();
}

WorkShop有两个BufferBlocks:一个用于输入工作请求,另一个用于输出完成的工作。车间有一个或多个线程,它们在输入BufferBlock处等待,直到作业到达。执行Work,并在完成后将作业发布到输出BufferBlock

class WorkShop
{
    public WorkShop()
    {
         this.workRequests = new BufferBlock<IWork>();
         this.finishedWork = new BufferBlock<IWork>();
         this.frontOffice = new FrontOffice(this.workRequests, this.finishedWork);
    }

    private readonly BufferBlock<IWork> workRequests;
    private readonly BufferBlock<IWork> finishedWork;
    private readonly FrontOffice frontOffice;

    public FrontOffice {get{return this.frontOffice;} }

    public async Task StartWorkingAsync(CancellationToken token)
    {
        while (await this.workRequests.OutputAvailableAsync(token)
        {   // some work request at the input buffer
            IWork requestedWork = this.workRequests.ReceiveAsync(token);
            requestedWork.DoWork();
            this.FinishedWork.Post(requestedWork);
        }
        // if here: no work expected anymore:
        this.FinishedWork.Complete();
    }

    // function to close the WorkShop
    public async Task CloseShopAsync()
    {
         // signal that no more work is to be expected:
         this.WorkRequests.Complete();
         // await until the worker has finished his last job for the day:
         await this.FinishedWork.Completion();
    }
}

TODO:对CancellationToken.CancellationRequested的正确反应
TODO:对工作引发的异常的正确反应
TODO:决定是否使用多个线程进行工作

FrontOffice具有一个异步功能,该功能可以接收工作,将工作发送到WorkRequests并等待工作完成:

public async Task<IWork> OrderWorkAsync(IWork work, CancellationToken token)
{
    await this.WorkRequests.SendAsync(work, token);
    IWork finishedWork = await this.FinishedWork.ReceivedAsync(token);
    return finishedWork;
}

因此,您的流程创建了一个WorkShop对象,并启动了一个或多个将开始工作的线程。

只要任何线程(包括您的主线程)需要以异步等待方式执行一些工作:

  • 创建一个保存输入参数和DoWork函数的对象
  • 向FrontOffice咨询WorkShop
  • 等待OrderWorkAsync

class InformationGetter : IWork
{
     public int Id {get; set;}                     // the input Id
     public Data FetchedData {get; private set;}   // the result from Remoting.GetData(id);
     public void DoWork()
     {
         this.FetchedData = remoting.GetData(this.Id);
     }
}

最后是遥控器的异步版本

async Task<Data> RemoteGetDataAsync(int id)
{
     // create the job to get the information:
     InformationGetter infoGetter = new InformationGetter() {Id = id};

     // go to the front office of the workshop and order to do the job
     await this.MyWorkShop.FrontOffice.OrderWorkAsync(infoGetter);
     return infoGetter.FetchedData;
}