等待手动创建的任务正在冻结ASP.NET应用

时间:2020-02-13 00:26:49

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

我做了一个队列,其中包含要执行的任务。用new Task()方法中的Returns手动创建一些任务后,我的整个应用程序挂起-await current;。任务的主体甚至没有触发。

ConfigureAwait(false)没有帮助。

队列中的第一个任务不是我创建的,而是其他框架正在成功执行并返回值。我的-不。我尝试添加Task.CompletedTask,然后它起作用了。我不明白为什么我什至无法达到包含_output分配任务的内容。

IDE debugger code screenshot

--- 更新 ---

当我使用以下代码时,该代码有效。使用await不会。有什么想法吗?

current.Start();
current.Wait();

原始代码

private readonly Queue<Task> _pipe;

public IPipeBuilder<TOutput> Returns(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
   _pipe.Enqueue(new Task(() => // this task causes a problem and breakpoint isn't hit inside 
   {
      _output = outputBuilder(_results);
   }));

   return this;
}

public async Task<TOutput> Execute()
{
   Task current;

   while (_pipe.TryDequeue(out current))
   {
      if (current.IsCommandExecution())
      {
         IExecutionResult result = await (Task<IExecutionResult>)current; // this awaits successfully
         _results.Add(result);
      }
      else
      {
         await current; // hangs here
      }
   }

   return await Task.FromResult(_output);
}

用法

[HttpGet("eventflow/pipe/issue/add/{title}")]
public async Task<IActionResult> PipeAction(string title)
   => Ok(
      await Pipe<IExecutionResult>()
           .Validate(title)
           .Handle<AddIssueCommand>(IssueId.New, title)
           .Returns(results => results.First())
           .Execute());

2 个答案:

答案 0 :(得分:2)

You should never use the Task constructor。在ASP.NET上,它具有两倍的效果,因为构造的任务始终是委托任务,interfere with the ASP.NET usage of the thread poolawait挂起的实际原因是因为manually-created tasks need to be started

如果您需要将同步工作包装到Task中以与异步任务一起工作,则应使用Task.CompletedTaskTask.FromException

private static Task SynchronousWork(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
  try { _output = outputBuilder(_results); return Task.CompletedTask; }
  catch (Exception ex) { return Task.FromException(ex); }
}

public IPipeBuilder<TOutput> Returns(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
  _pipe.Enqueue(SynchronousWork(outputBuilder));
  return this;
}

但是,请注意,它会立即执行outputBuilder ,由于其对_results_output的副作用,因此可能不希望这样做。如果需要延迟执行队列 ,则需要将队列中的类型从Task更改为Func<Task>。然后,您可以像这样添加它:

public IPipeBuilder<TOutput> Returns(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
  _pipe.Enqueue(() =>
  {
    try { _output = outputBuilder(_results); return Task.CompletedTask; }
    catch (Exception ex) { return Task.FromException(ex); }
  });

  return this;
}

,您将通过一次调用每个委托并检查返回的任务来消耗它:

public async Task<TOutput> Execute()
{
  while (_pipe.TryDequeue(out var currentFunc))
  {
    var currentTask = currentFunc();
    if (currentTask.IsCommandExecution())
    {
      IExecutionResult result = await (Task<IExecutionResult>)currentTask;
      _results.Add(result);
    }
    else
    {
      await currentTask;
    }
  }

  return _output;
}

答案 1 :(得分:0)

好的,谢谢。我结束了这样的课程,并且像您说的那样Queue<Func<Task>>

public sealed class SyncTaskWrapper
{
    private Func<Task> _action;

    public SyncTaskWrapper(Action action)
        => _action = CreateFunc(action);

    private static Func<Task> CreateFunc(Action action)
        => () =>
        {
            try
            {
                action();

                return Task.CompletedTask;
            }
            catch (Exception exception)
            {
                return Task.FromException(exception);
            }
        };

    public static implicit operator Func<Task>(SyncTaskWrapper @this)
        => @this._action;
}

随用法

_pipe.Enqueue(new SyncTaskWrapper(() =>
    _output = outputBuilder(_results)));
相关问题