【问题标题】:How to wait for a task to complete?如何等待任务完成?
【发布时间】:2019-06-05 20:10:55
【问题描述】:

问题出在输出上

当前输出为

工作等待

工作开始

延迟开始

工作完成

延迟结束

我想去哪里

工作等待

工作开始

延迟开始

延迟结束

工作完成

    private static Task _task;

    public static  void Main(string[] args)
    {

        Call().Wait();
    }

    private static async Task Call()
    {
        _task = new Task(async () => { await Pause(); });
        var timer = new Timer();
        timer.Interval = 10000;
        timer.Enabled = true;
        timer.AutoReset = false;
        timer.Elapsed += Timer_Elapsed;
        Console.WriteLine("job waiting");
        await _task;
        Console.WriteLine("job done");
        Console.ReadKey();
    }

    public static async Task Pause()
    {
        Console.WriteLine("Delay started");
        await Task.Delay(10000);
        Console.WriteLine("Delay ended");

    }
    private static void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        Console.WriteLine("job starting");

        _task.Start();

    }

【问题讨论】:

  • Task 构造函数并不意味着传递异步任务,你甚至不需要。 _task = new Task(() => Pause());
  • 任务不是线程。很少有任何理由使用Task 构造函数。 所有示例、教程、课程、文章显示使用 Task.Run(或 2012 年之前的 Task.Factory.StartNew)开始新任务。
  • 如果你想等待Pause,你可以在Call()中写await Pause()Task.Delay() 不会阻塞,所以没有理由在另一个线程中启动它。使用await Task.Run(()=>Pause()) 意味着Console.WriteLine 语句将在后台线程中运行,Task.Delay 的工作方式与之前相同
  • 嗨,我很欣赏上面的所有答案,但我这样做的原因是因为我需要在某个时间点创建一个任务,但稍后再启动它,所以你看我有必要这样做通过构造函数创建任务\
  • 任务通常在“热”状态下创建,这意味着它们已经启动。据我所知,冷任务不适用于await。是否可以不创建冷任务,而是创建委托,然后将此委托用作热任务的操作?

标签: c# .net task-parallel-library


【解决方案1】:

我这样做的原因是我需要在某个时间点创建一个任务但稍后再启动它

在这种情况下,合适的工具是 Func<Task>,而不是 Task 构造函数。 The Task constructor should never, ever be used.

在这种情况下,由于Task 构造函数不理解async 委托,因此您的输出并不理想。因此传递给Task 构造函数的async 委托被视为async void,这使得等待它不可行。

解决方法是使用async-aware 工具。在这种情况下,async-compatible delegate。由于Task 对象在计时器事件触发之前不存在,因此您还需要将该对象作为“信号”传递给另一个方法,这有点尴尬。大多数现实世界的代码不需要这样做:

private static Func<Task> _func;
private static TaskCompletionSource<Task> _taskSignal = new TaskCompletionSource<Task>();
private static async Task Call()
{
  _func = Pause;
  var timer = new Timer();
  timer.Interval = 10000;
  timer.Enabled = true;
  timer.AutoReset = false;
  timer.Elapsed += Timer_Elapsed;
  Console.WriteLine("job waiting");
  var task = await _taskSignal.Task; // Get the Task instance representing Pause
  await task; // Wait for Pause to finish
  Console.WriteLine("job done");
  Console.ReadKey();
}

public static async Task Pause()
{
  Console.WriteLine("Delay started");
  await Task.Delay(10000);
  Console.WriteLine("Delay ended");
}

private static void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
  Console.WriteLine("job starting");
  var task = _func(); // Start Pause running
  _taskSignal.TrySetResult(task); // Pass the Pause task back to Main
}

【讨论】:

  • link 并没有真正解释为什么永远不应该使用 Task 构造函数。它解释了为什么通常不需要它。但如果有人出于某种原因想要await 一个冷门任务,会发生什么坏事呢?
  • @TheodorZoulias:Task 构造函数的唯一用例是用于冷委托任务,这在 9 年前并不是一个好主意,此后一直被忽略。 ActionFunc&lt;Task&gt; 在 9 年前是更好的选择,现在仍然如此。如果你await 是一个冷任务,await 永远不会完成。
  • 我测试了等待冷任务的情况,并且await do 完成,前提是任务同时由计时器或其他任务启动。所以等待冷任务的缺点一定是别的。
  • 如果你开始冷任务,它就不再冷了。
  • Cold are called 在非计划的 Created 状态下开始其生命周期的任务。不是永远没有计划的任务!
【解决方案2】:

一种解决方案是使任务主体同步。换句话说,而不是这样:

_task = new Task(async () => { await Pause(); });

...这样做:

_task = new Task(() => { Pause().Wait(); });

如果你想让它保持异步,那么你必须找到一种方法来等待任务的主体。这可以通过创建Task&lt;Task&gt; 并等待任务结果而不是主任务本身来实现:

_task = new Task<Task>(async () => { await Pause(); });
//...
await _task.Result;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多