我应该等待使用TaskCompletionSource的同步Task <t>吗?</t>

时间:2014-01-31 00:22:54

标签: c# .net asynchronous async-await

我正在尝试编写一些包含非基于任务的库(MSDeploy API)的库代码。基本上,我从远程服务器获取东西并进行分析。我正在使用TaskCompletionSource包装同步MSDeploy API b / c虽然它不是基于任务的,但我可以获得CancellationToken支持来使用该库的CancelCallback委托。 “获取东西”代码作为同步API公开,但它受I / O限制。分析代码是I / O&amp; CPU密集型并且已经基于任务。它不会产生任何任务/线程(我将在调用代码中执行此操作)。

我最近一直在阅读很多关于这些内容的博客,但我仍然试图了解async / await / ConfigureAwait()并找出编写和调用复合方法的最佳方法( GetAndAnalyzeStuff)。几个问题:

  1. 是否在GetAndAnalyzeStuff方法中等待基于TaskCompletionSource的调用和TPL异步调用,或者只是异步调用(我应该在调用GetStuff时使用.Wait())。
  2. 你能告诉我在某个地方是否需要ConfigureAwait(false)吗?如果是这样,你怎么知道?
  3. 如何在调用代码(下面的Main())中处理Task.Run()调用?我更喜欢这个在后台运行,我认为在库/ API代码中启动任务是不好的做法。我假设Task.Run比Task.Factory.StartNew b / c更合适我正在做异步的东西。
  4. 下面的代码(使用.NET 4.5.1):

    public Task<Stuff> GetStuff(CancellationToken token) 
    {
        var tcs = new TaskCompletionSource<Stuff>(tcs);
        try 
        {
            var stuff = new Stuff();
            using (var stuffGetter = new StuffGetter()) 
            {
                stuffGetter.CancelCallback = () => cancellationToken.IsCancellationRequested;
    
                for (var x = 0; x < 10; x++) 
                {
                    if (token.IsCancellationRequested) 
                    {
                        tcs.SetCancelled();
                        return tcs.Task;
                    }
    
                    // StuffGetter's doing IO & would ideally have an API w/Task<Stuff>, but
                    // it doesn't and it's not my code.
                    var thing = stuffGetter.GetSomething();
                    stuff.AddThing(thing);
                }
                tcs.SetResult(stuff);
            }
        }
        catch (SomeNonTplFriendlyExceptionThatMeansSomethingWasCancelled sntfetmswc)
        {
            tcs.SetCancelled();        
        }
        catch (Exception exc) 
        {
            tcs.SetException(exc);
        }
        return tcs.Task;
    }
    
    public async Task<StuffAnalysis> AnalyzeStuff(Stuff stuff, CancellationToken token) 
    {
        var allTasks = new List<Task>();
    
        using (var stuffAnalyzer = new StuffAnalyzer())
        {
            foreach (var thing in stuff) 
            {
                allTasks.Add(stuffAnalyzer.AnalyzeThingAsync(thing));
            }
    
            await Task.WhenAll(allTasks);
        }
    
        return stuffAnalyzer.Result;
    }
    
    public async Task<StuffAnalysis> GetAndAnalyzeStuff(CancellationToken token)
    {
        var stuff = await GetStuff(token); // or should this be GetStuff.Wait() or maybe GetStuff.Result?
        var analysis = await AnalyzeStuff(stuff, token);
        return analysis;
    }
    
    public static void Main() 
    {
        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        try 
        {
            // Since GetStuff is synchronous and I don't want to block, use Task.Run() -- right?
    
            var task = Task.Run(() => GetAndAnalyzeStuff(cts.Token), cts.Token);
            // or var task = Task.Run(async () => await GetAndAnalyzeStuff(cts.Token), cts.Token); ?
    
            Console.WriteLine("Getting and anlyzing stuff in the background.");
    
            await task;
        } 
        catch (OperationCancelledException)
        {
            Console.WriteLine("There was a problem.");
        }
        catch (Exception)
        {
            Console.WriteLine("There was a problem.");
        }
    }
    

1 个答案:

答案 0 :(得分:6)

由于您没有自然异步API,因此我不建议使用TaskCompletionSource<T>。您可以对同步API使用完整的CancellationToken支持,如下所示:

public Stuff GetStuff(CancellationToken token) 
{
    var stuff = new Stuff();
    using (var stuffGetter = new StuffGetter()) 
    {
        stuffGetter.CancelCallback = () => token.IsCancellationRequested;

        for (var x = 0; x < 10; x++) 
        {
            token.ThrowIfCancellationRequested();
            var thing = stuffGetter.GetSomething();
            stuff.AddThing(thing);
        }
        return stuff;
    }
}

当您编写任务返回方法时,遵循TAP guidelines非常重要。在这种情况下,命名约定意味着您的AnalyzeStuff应该被称为AnalyzeStuffAsync

  

你能告诉我在某处是否需要ConfigureAwait(false)吗?如果是这样,你怎么知道?

除非您需要方法中的上下文,否则您应该使用ConfigureAwait(false)(“上下文”通常是客户端应用程序的UI上下文,或ASP.NET应用程序的请求上下文)。您可以在我的MSDN article on async best practices

中找到更多信息

因此,假设StuffAnalyzer.Result没有任何类型的UI线程依赖或类似的东西,我会写AnalyzeStuffAsync

public async Task<StuffAnalysis> AnalyzeStuffAsync(Stuff stuff, CancellationToken token) 
{
  var allTasks = new List<Task>();

  using (var stuffAnalyzer = new StuffAnalyzer())
  {
    foreach (var thing in stuff) 
    {
      allTasks.Add(stuffAnalyzer.AnalyzeThingAsync(thing));
    }

    await Task.WhenAll(allTasks).ConfigureAwait(false);
  }

  return stuffAnalyzer.Result;
}

您的GetAndAnalyzeStuffAsync是一个更复杂的情况,您在该方法中同时拥有阻塞代码和异步代码。在这种情况下,最好的方法是将其作为异步API公开,但要注明它是阻塞的明确注释。

// <summary>Note: this method is not fully synchronous; it will block the calling thread.</summary>
public async Task<StuffAnalysis> GetAndAnalyzeStuffAsync(CancellationToken token)
{
  var stuff = GetStuff(token);
  var analysis = await AnalyzeStuff(stuff, token).ConfigureAwait(false);
  return analysis;
}

可简化为:

// <summary>Note: this method is not fully synchronous; it will block the calling thread.</summary>
public Task<StuffAnalysis> GetAndAnalyzeStuffAsync(CancellationToken token)
{
  var stuff = GetStuff(token);
  return AnalyzeStuff(stuff, token);
}
  

如何在调用代码(下面的Main())中处理Task.Run()调用?

您正确使用它。我在博客上描述了this situation。在控制台应用程序中,使用这样的Task.Run并不常见,但是这样做没有错误Task.Run通常用于释放UI应用程序中的UI线程。

  

我认为Task.Run比Task.Factory.StartNew更适合b / c我正在做异步的东西。

是的,是的。