【问题标题】:Task returns IsCompleted == true before it is actually completed任务在实际完成之前返回 IsCompleted == true
【发布时间】:2021-10-25 08:30:45
【问题描述】:

我有两个任务组,我想在 ASP.NET 应用程序中一个接一个地运行。

isLogin = false;

Task[] firstBatch = 
{
    new Task(async () => isLogin = await webserviceclient.Login())
};
firstBatch.ForEach(t => t.Start());
Task.WaitAll(firstBatch);



Task[] secondBatch = 
{
    new Task(async () => UsersList1 = isLogin ? await webserviceclient.GetAllUsers(1) : null),
    new Task(async () => UsersList2 = isLogin ? await webserviceclient.GetAllUsers(2) : null)
};
secondBatch.ForEach(t => t.Start());
Task.WaitAll(secondBatch);

webserviceclient.Login() 中,我已经调用了await _client.GetAsync(_baseUri).ConfigureAwait(false)。至于webserviceclient.GetAllUsers(),我做什么都没关系,因为它从不运行。我想在 await _client.GetAsync(_baseUri) 被称为 firstBatch[0].IsCompleted for unknown reason 之后设置为 true 并且 secondBatch 开始执行。 await _client.GetAsync(_baseUri)Task.WaitAll(secondBatch); 命令之后实际上停止等待。这显然不是我想要的。

看起来像是某种死锁,但我看不出问题出在哪里。

【问题讨论】:

  • 手动创建大量Tasks,分别Starting它们并混合async/await。通常是解决问题的方法。我强烈怀疑,如果您在运行时检查所有实际类型,您会发现某些您认为很简单的项目 Tasks 实际上是 Task<Task> - 所以当外部任务完成时,它没有说明内部任务是否任务是。
  • 您可以尝试从您的调用 API 中删除 ConfigureAwait ,因为文档说该任务包含在另一个任务中。
  • 永远不要使用new Task,除非您完全清楚自己在做什么,否则这是您的问题以及您将异步 lambda 传递给 Action 的事实将在未观察到的情况下运行,而是使用 Task.Run
  • 任务不是线程。调用.Start() 开始的冷任务从来没有任何充分的理由。 webserviceclient.Login() 已经返回一个任务。如果您想登录多个服务,您可以将这些 任务放入一个列表并使用var loginResults=Task.WhenAll(tasks) 等待所有这些任务 检查它们的结果。您当前的代码甚至没有这样做,因为isLogin 的值是任意的——它将是最后一个要完成的任务返回的值。甚至不是数组中的最后一个任务

标签: c# asp.net async-await task-parallel-library


【解决方案1】:

我会重写你的代码如下:

var isLogin = false;
isLogin = await webserviceclient.Login();

if(isLogin)
{
    var secondBatch = new []
    {
        webserviceclient.GetAllUsers(1),
        webserviceclient.GetAllUsers(2)
    }; 
    var userResults = await Task.WhenAll(secondBatch);
}

【讨论】:

  • 我可以看到两个问题。第二个是Task.WhenAll(Task[])方法返回一个Task,而不是Task<Result>,所以它不能为userResults变量传递一个值。
  • @TheodorZoulias 第一个问题是什么?至于第二个,我认为您正在查看该页面上的错误超载? Task.WhenAll(IEnumerable<Task<TResult>>) 返回 Task<TResult[]>。在上面的例子中,userResults[0] 的结果是await webserviceclient.GetAllUsers(1)
  • 同意,这不会与等待一起编译,一旦你等待 GetAllUsers(x),它就不再是一个任务,也不能分配给一个任务数组
【解决方案2】:

cmets 中的答案解决了这个问题。谢谢。

isLogin = false;

Task[] firstBatch = 
{
    Task.Run(async () => isLogin = await webserviceclient.Login())
};
Task.WaitAll(firstBatch);



Task[] secondBatch = 
{
    Task.Run(async () => UsersList1 = isLogin ? await webserviceclient.GetAllUsers(1) : null),
    Task.Run(async () => UsersList2 = isLogin ? await webserviceclient.GetAllUsers(2) : null)
};
Task.WaitAll(secondBatch);

关于var loginResults=Task.WhenAll(tasks) 的评论我同意,但无论如何我从来没有打算在那里运行多个webserviceclient.Login()。由于这只是一个示例,第一组中的任务不同,结果与同一组中的其他任务无关。

【讨论】:

  • “第一批”代码等价于:bool isLogin = Task.Run(() => webserviceclient.Login()).Result;。这不是好的代码。您正在阻塞两个ThreadPool 线程,以便完全完成可能doesn't require any thread 的工作。你有一个可扩展性杀​​手。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-07
  • 2015-06-16
  • 1970-01-01
  • 2021-02-15
  • 2015-09-21
  • 2020-05-28
相关资源
最近更新 更多