C# 重构循环以调用异步进程

C# 重构循环以调用异步进程,c#,asynchronous,C#,Asynchronous,我有一个循环的过程。在循环的每个迭代过程中,它调用外部web服务,然后向EntityFramework存储库添加一个对象。对外部服务的调用被包装在一个静态方法中。通常,循环只有一个或两个迭代,但在UI中最多可以有4个迭代。每个迭代代表一个保险报价 看起来,将其重构为一个异步进程将使其受益。我如何设置它,使每次迭代都发生在一个单独的线程中,提交将等待所有线程完成 public class ProcessRequest { private IUnitOfWork = unitOfWork;

我有一个循环的过程。在循环的每个迭代过程中,它调用外部web服务,然后向EntityFramework存储库添加一个对象。对外部服务的调用被包装在一个静态方法中。通常,循环只有一个或两个迭代,但在UI中最多可以有4个迭代。每个迭代代表一个保险报价

看起来,将其重构为一个异步进程将使其受益。我如何设置它,使每次迭代都发生在一个单独的线程中,提交将等待所有线程完成

public class ProcessRequest
{
    private IUnitOfWork = unitOfWork;

    public ProcessRequest(IUnitOfWork uow)
    {
        unitOfWork = uow;
    }

    public void Execute(MyRequestParams p)
    {
        foreach (Quote q in p.Quotes)
        {
            q.Premium = QuoteService.GetQuote(q);
            unitOfWork.GetRepository<Quote>().Add(q);
        }

        unitOfWork.Commit();
    }
}

public static class QuoteService
{
    public static decimal GetQuote(Quote quote)
    {
        //I've simplified proprietary code to single line that calls an external service
        return ExternalWebService.GetQuote(quote.Deductible);
    }
}

您要问两件不同的事情:一是如何并行执行循环,其中每个迭代都可能发生在单独的线程上;这与作为异步进程执行整个循环完全不同,这意味着启动它的线程不会等待它完成。我假设您是指第一个,也就是说,您希望并行化循环中的迭代,但仍然会阻塞,直到所有迭代都完成

在不了解运行此操作的上下文的情况下,一种简单的方法是使用并行扩展,特别是并行Foreach:

public void Execute(MyRequestParams p)
{
  Parallel.ForEach(p.Quotes, q => {
    q.Premium = QuoteService.GetQuote(q);
    unitOfWork.GetRepository<Quote>().Add(q);
  });

  unitOfWork.Commit();
}
或者类似于:

public void Execute(MyRequestParams p)
{
  Parallel.ForEach(p.Quotes, q => {
    q.Premium = QuoteService.GetQuote(q);
  });

  unitOfWork.GetRepository<Quote>().AddAll(p.Quotes);
  unitOfWork.Commit();
}

这在很大程度上取决于您所处理的线程安全性。

如果您的大部分工作是I/O,我不确定是否会启动一个新线程,因为您在等待服务/DB回复时浪费了大量时间

我会尝试采用异步方法:

public async Task Execute(MyRequestParams p)
{
  foreach (var quote in p.Quotes)
  {
     //Of course, you'll need an async endpoint.
     var q.Premium = await QuoteService.GetQuoteAsync(q);
  }

  unitOfWork.GetRepository<Quote>().AddAll(p.Quotes);
  await unitOfWork.SaveChangesAsync();
}
使用这种方法,您可以节省启动新线程并让它们大部分时间处于空闲状态的开销。
希望这有意义,当然您必须访问Web服务的异步端点,并使用Entity Framework v6。

这是WPF、WinForms、Webforms、MVC、Web API吗?与答案非常相关这是在n层解决方案的业务层中。业务层由MVC前端调用。已经讨论过将业务层转换为Restful API,然后UI调用该API。用户界面是低流量的,目前只有少数用户,但在不久的将来可能会扩大。你是对的,我没有问清楚这个问题。我想说的是,每个迭代都应该是异步的,不应该等到一个迭代停止后再开始下一个迭代。你说得比我好。谢谢你的回复,我喜欢你的解决方案。我也喜欢这个解决方案。我必须仔细阅读parelle.ForEach和Task/Await之间的区别。我将确认我有一个Web服务的异步内点。再次感谢。基本上:使用async/await一个接一个地连续运行循环,但是每个等待部分都可以选择在单独的线程上调用,这使您的原始线程不必担心它。如果原始线程是Web服务器工作线程,那么这是相关的,在这种情况下,您不想让它等待。Parallel.ForEach解决方案仍然保留原始工作线程,同时等待其他线程并发执行工作,而不是串行执行。它们实际上是在解决不同的问题,如果需要可以结合使用。Async/Await使您能够以同步方式编写基于I/O的异步工作。假设您希望通过网络发送HTTP消息。生成HTTP消息并将其发送到web服务,该点上的线程位于那里,等待web服务检索响应。Async使您能够告诉该请求听着,我现在将返回给我的呼叫方,但请在您完成后告诉我,我会跳回到我们结束的地方。这使您能够在基本上阻止I/O操作的工作上不占用任何线程资源。