重构循环以调用异步进程

时间:2014-04-10 20:33:09

标签: c# asynchronous

我有一个循环过程。在循环的每次迭代期间,它调用外部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);
    }
}

2 个答案:

答案 0 :(得分:0)

你问两个不同的东西:一个是如何并行执行循环 ,其中每个迭代(可能)在一个单独的线程上发生;这与将整个循环作为异步进程执行完全不同,这意味着启动它的线程不会等待它完成。我假设你的意思是第一个,即你希望在循环中并行化迭代但仍然阻塞直到所有这些迭代完成。

在不知道运行它的上下文的情况下,一种直接的方法是使用Parallel Extensions,特别是Parallel 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();
}

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

答案 1 :(得分:0)

如果您的大部分工作都是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。