在保持同步方法签名的同时使用Asynchronous方法

时间:2016-01-28 17:39:00

标签: c# asynchronous task-parallel-library

我有一个目前具有以下结构的应用程序

foreach (var req in requests)
{  
   var myReq = req;
   var t = Task.Factory.StartNew(() =>
   {
      var result = valueService.Calc(myReq);
      return result;
   }
   taskArray.Add(t)
   t.Start();
}

Task.WaitAll(taskArray);

在我的应用程序中,我在每项任务中使用t.Result。

现在这很有效,因为我的Calc(myReq)是一种同步方法。

但是,现在我已经被要求我的valuationService.Calc(myReq)将调用异步方法,结果将以Callback方法返回。

class ValuationService
{
   ....
   public MyResult Calc(MyRequest myReq)
   {
      obj.Callback = MyCallback;
      obj.CalcAsync()
   }

   public void Callback(MyResult result)
   {
     ...
   }

所以我想要实现的是:让valuService.Calc(MyReq)保持其签名并以某种方式阻塞,直到运行回调方法。然后将回调的结果传递回Calc方法,以便它可以传回数据。

2 个答案:

答案 0 :(得分:2)

  

所以我想要实现的是:让valuService.Calc(MyReq)保持其签名并以某种方式阻塞,直到运行回调方法。

如果那是你真正想要的,那么你可以用一个简单的ManualResetEvent来做到这一点:

public MyResult Calc(MyRequest myReq)
{
   var mre = new ManualResetEvent(false);
   obj.Callback = result => MyCallback(result, mre);
   obj.CalcAsync();
   mre.WaitOne();
}

public void Callback(MyResult result, ManualResetEvent mre)
{
  try
  {
    ...
  }
  finally
  {
    mre.Set();
  }
}

但是,请注意,现在代码将使用线程池线程只是为了处于等待状态,直到调用回调。这可能会影响您的表现。

另一种解决方案是接受异步工作的性质(即,不要在异步操作上浪费线程阻塞)。如果您同时具有CPU绑定和异步工作,则可以将TPL Dataflow视为一种解决方案。

答案 1 :(得分:0)

对于await来说,这实际上是微不足道的:

async Task DoAsync(Action callback)
{
  await WhateverAsync().ConfigureAwait(false);

  callback();
}

你会想要确保回调中没有例外 - 因为从未等待任务,它们将被吞噬。根据需要添加自定义错误处理。

如果您没有await可用,则可以使用延续执行类似的操作:

WhateverAsync().ContinueWith(t => callback());

它只是使错误处理有点棘手:)

最终结果是你有Task可以在需要时同步等待 - 这似乎正是你想要做的。

如果您不是执行回调的人,则需要使用TaskCompletionSource来提供自己的Task。在回调方法中,您将使用tcs.SetResult(result)(或SetException,如果需要)。最简单的方法是使用闭包:

var tcs = new TaskCompletionSource<MyResult>();
obj.Callback = result => { doWhateverYouNeed(); tcs.SetResult(result); };

return tcs.Task.GetAwaiter().GetResult();