【问题标题】:Task.Run( asnyc () => ) Blocking/Not Running All Tasks C#Task.Run( asnyc () => ) 阻塞/不运行所有任务 C#
【发布时间】:2019-03-17 05:22:45
【问题描述】:

问题是我尝试多次运行 RunPrivateMethod(),但遇到阻塞问题,或者在使用 async/await 时直接无法正常工作。而不是在这里分享每一次尝试,我只是把我当前的版本。

在 RunPrivateMethod() 中,exeProcess.WaitForExit() 方法是一个外部程序,基本上是读取/写入/处理/输出数据。我尝试将此作为异步任务运行,但它不起作用。

我不知道这是否合理,但我想限制一次启动的任务数量,所以我在每个案例块的末尾放入了 Task.WaitAll()。案例 (1) 和案例 (2) 始终都会运行。

所以,这是我的代码。目前,它在加载每个任务时会阻塞。然后似乎只有最后一个任务在运行。如果我取出所有任务语句并正常运行一切,它就可以正常工作。

我真的非常感谢您对此的任何意见或帮助。我的大部分测试最终都会锁定我的系统和键盘。

public void RunSomeTasks(int group)
    {

        switch (group)
        {

            case (1):
                {
                    Task.Run(async () => await RunAsyncMethod(param1, param1, group));
                    Task.Run(async () => await RunAsyncMethod(param1, param2, group));
                    Task.Run(async () => await RunAsyncMethod(param1, param3, group));
                    Task.WaitAll();
                    break;
                }
            case (2):
                {
                    Task.Run(async () => await RunAsyncMethod(param2, param1, group));
                    Task.Run(async () => await RunAsyncMethod(param2, param2, group));
                    Task.Run(async () => await RunAsyncMethod(param2, param3, group));
                    Task.WaitAll();
                    break;
                }
        }
    }

    async Task RunAsyncMethod(string var1, string var2, string varGroup)
    {           

        ////////////////////////////////////////////
        // Use ProcessStartInfo class
        ProcessStartInfo startInfo = new ProcessStartInfo();
        startInfo.CreateNoWindow = false;
        startInfo.UseShellExecute = false;
        startInfo.FileName = "SomeOutsideEXE";
        startInfo.WindowStyle = ProcessWindowStyle.Hidden;
        startInfo.Arguments = var1 + " " + var2 + " " + varGroup;
        using (Process exeProcess = Process.Start(startInfo))
        {
            // did not work -> Task.Run(() => exeProcess.WaitForExit()).Wait();
            exeProcess.WaitForExit();
        }
    }
}

我为此工作了无数小时并阅读了 Cleary 的书,最新版本是这篇文章的一个版本:Aync/Await action within Task.Run() 目前的结果是,尽管 exeProcess 的启动时间正确,但每组中的最后一个任务仍然有效。我无法在键盘运行时使用它。

我显然为 RunSomeTasks() 尝试了一个直接的异步方法,然后首先等待 RunAsyncMethod。我真的可以使用一些帮助,是的,我已经知道我不知道我在做什么,尽管我花了很长时间阅读和试错。

【问题讨论】:

  • 当你在RunSomeTasks时,此时会阻塞Task.WaitAll();
  • 我明白了,但线程将被阻止。 1) 这是什么类型的应用程序(控制台、winform 等)? 2)问题是什么? 3)你想做什么,实现什么?
  • 使RunSomeTasks 异步,并使调用此方法的事件处理程序也为async。这样主线程 (UI) 就会对用户点击等做出反应。
  • 您不能等待 exeProcess.WaitForExit。但是您可以使用 TaskCompletionSource 结合Process.Exited 事件
  • 听起来你的任务是 IO 绑定的。 不要将 IO 绑定任务移到工作线程上。将它们保留在 UI 线程上并使用异步 IO。否则你正在做的是雇佣工人,然后付钱让他们睡觉!

标签: c# asynchronous async-await task-parallel-library


【解决方案1】:

我稍微修改了您的示例,这应该可以防止 UI 锁定。请注意,我在按钮单击中添加了async。也使用 WhenAll 并传入已启动的任务,而不是使用 WaitAll。 (更新为完全异步模式)

    private async void button1_Click(object sender, EventArgs e)
    {
        try
        {
            await RunSomeTasks(1);
            await RunSomeTasks(2);
            lblStatus.Text = "done!";
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }

    public async Task RunSomeTasks(int group)
    {
        switch (group)
        {
            case (1):
                {
                    var t1 = RunMethodAsync(param1, param1, group);
                    var t2 = RunMethodAsync(param1, param2, group);
                    var t3 = RunMethodAsync(param1, param3, group);
                    await Task.WhenAll(t1, t2, t3).ConfigureAwait(false);
                    break;
                }
            case (2):
                {
                    var t1 = RunMethodAsync(param2, param1, group);
                    var t2 = RunMethodAsync(param2, param2, group);
                    var t3 = RunMethodAsync(param2, param3, group);
                    await Task.WhenAll(t1, t2, t3).ConfigureAwait(false);
                    break;
                }
        }
    }

    async Task RunMethodAsync(string var1, string var2, int varGroup)
    {
        ////////////////////////////////////////////
        // Use ProcessStartInfo class
        ProcessStartInfo startInfo = new ProcessStartInfo();
        startInfo.CreateNoWindow = false;
        startInfo.UseShellExecute = false;
        startInfo.FileName = "SomeOutsideEXE";
        startInfo.WindowStyle = ProcessWindowStyle.Hidden;
        startInfo.Arguments = var1 + " " + var2 + " " + varGroup;

        using (Process exeProcess = new Process())
        {
            var cts = new TaskCompletionSource<int>();

            exeProcess.StartInfo = startInfo;
            exeProcess.EnableRaisingEvents = true;

            exeProcess.Exited += (sender, e) =>
            {
                try
                {
                    cts.SetResult(exeProcess.ExitCode);
                }
                catch (Exception ex)
                {
                    cts.SetException(ex);
                }
            };

            exeProcess.Start();

            await cts.Task.ConfigureAwait(false);
        }
    }

【讨论】:

  • 当提到 Stephen Cleary 时,您永远不应该忘记 ConfigureAwait(false) 何时何地 :o)
  • 可能需要钙化,为什么 await cts.Task 不应该在 exeProcess.Exited 处理程序中?否则这个答案很好。
  • 其实这里要ConfigureAwait(true),因为要在ui线程上继续,否则lblStatus.Text = "done!"会失败。
  • @CodingYoshi,如果将 await cts.Task 放在 exeProcess.Exited 处理程序中,则 RunMethodAsync 方法在退出 using 块之前不会等待 cts.Task。当然,您仍将启动进程,但您将失去任何等待事件的能力。
  • @Haukinger,我认为将 ConfigureAwait(false) 放在 WhenAllcts.Task 上不会改变 await RunSomeTasks(1) 的延续上下文。换句话说,如果我们不将 ConfigureAwait(false) 放在对 RunSomeTasks(1) 的调用上,那么当它继续时,它将在 UI 上下文中继续,无论我们是否将 ConfigureAwait(false) 放在 RunSomeTasks(1) 方法中或更进一步。
【解决方案2】:

我想出的最干净的方式来做一个awaitProcess.WaitForExit 就是这样,节省订阅events 等。

private async Task RunRubyScript(string filePath)
{
  await Task.Run(() =>
  {
    using (var proc = new Process())
    {
      var startInfo = new ProcessStartInfo(@"ruby");
      startInfo.Arguments = filePath;
      startInfo.UseShellExecute = false;
      startInfo.CreateNoWindow = true;
      proc.StartInfo = startInfo;
      proc.Start();
      proc.WaitForExit();
    }
  });
}

这是为了运行 ruby 脚本,但显然要相应地修改 ProcessStartInfo 以满足您的要求。

希望这会有所帮助!

【讨论】:

  • 这是个好主意,特里斯坦。我以前用过类似的东西,但不是这种形式。我喜欢它:)
猜你喜欢
  • 2020-12-21
  • 2021-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-16
  • 2013-06-23
相关资源
最近更新 更多