【问题标题】:async/await running on single threadasync/await 在单线程上运行
【发布时间】:2019-05-29 08:34:53
【问题描述】:

我有一个控制台应用程序,在某些情况下需要显示用户界面。此用户界面需要保持响应,因为它将包含加载 gif、进度条、取消按钮等。我有以下示例代码:

class Program
{
    static void Main(string[] args)
    {
        DoWork().GetAwaiter().GetResult();
    }

    private static async Task DoWork()
    {
        TestForm form = new TestForm();
        form.Show();

        string s = await Task.Run(() =>
        {
            System.Threading.Thread.Sleep(5000);
            return "Plop";
        });

        if (s == "Plop")
        {
            form.Close();
        }
    }
}

我希望从上面的代码中 TestForm 在关闭之前显示大约 5 秒,因为字符串的值为“Plop”,但是所发生的只是任务运行并且 if 语句永远不会到达。此外,TestForm 的 UI 不会保持响应。这段代码有什么问题?

【问题讨论】:

  • 这是 WinForms 还是控制台应用程序?似乎您需要在某处启动事件泵,例如Application.Run(form).
  • 据我所知,不能保证任务会在与 UI 线程分开的线程上运行,并且您永远不应该在任务中使用 Thread.Sleep,因为它是阻塞操作; stackoverflow.com/q/28314695
  • @JohnWu 这是一个控制台应用程序
  • @AlphaDelta 当我用 for int 循环替换 Thread.Sleep 时,我看到了相同的结果
  • 那么你有一个控制台应用程序,它向用户呈现表单?

标签: c# multithreading task


【解决方案1】:

所以我设法为此拼凑出一个肮脏的解决方案。这不是一个干净的解决方案,所以我仍然愿意接受建议,但对于我需要的,它可以正常工作

private static void DoWork()
    {
        TestForm form = new TestForm();

        Task formTask = Task.Run(() => form.ShowDialog());

        Task<string> testTask = Task.Run(() =>
        {
            for (int i = 1; i < 10; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine(i.ToString());
            }

            Console.WriteLine("Background task finished");
            return "Plop";
        });
        Console.WriteLine("Waiting for background task");

        testTask.Wait();

        if (testTask.Result == "Plop")
        {
            Dispatcher.CurrentDispatcher.InvokeAsync(() => form.Close());
        }

        Console.WriteLine("App finished");
    }

这会先输出'Waiting for background task',然后是Task的个数,然后在长流程完成后输出'Background task finished',同时关闭响应式UI表单

【讨论】:

    【解决方案2】:

    这是一个典型的死锁。当您的代码命中 await 时,控制返回到主线程,这是对 DoWork GetResult(); 的阻塞等待;当 Task.Run 线程完成时,控件尝试返回主线程,但它正在等待 DoWork 完成。这就是最后一个 If 语句永远不会执行的原因。

    但是除了死锁,你的代码中还有一个问题会让你的 UI 冻结。它的 form.Show() 方法。如果你删除所有与 async-await 和只使用表单,它仍然会冻结。问题是 Show 方法需要一个 Windows 消息循环,如果您创建一个 Windows.Forms 应用程序将提供该消息循环,但在这里您从没有消息循环的控制台应用程序启动表单。一种解决方案是使用 form.ShowDialog 来创建自己的消息循环。另一种解决方案是使用 System.Windows.Forms.Application.Run 方法,该方法为通过线程池线程创建的表单提供了一个获胜消息循环。我可以在这里为您提供一种可能的解决方案,但这取决于您在确定根本原因时如何构建代码。

    static void Main(string[] args)
        {
            TestForm form = new TestForm();
            form.Load += Form_Load;
    
            Application.Run(form);
    
        }
    
        private static async void Form_Load(object sender, EventArgs e)
        {
            var form = sender as Form;
            string s = await Task.Run(() =>
            {
                System.Threading.Thread.Sleep(5000);
                return "Plop";
            });
    
            if (s == "Plop")
            {
                form?.Close();
            }
        }
    

    【讨论】:

      【解决方案3】:

      好的,我确实将我的第一个答案标记为要删除,因为我放在那里的内容适用于 WPF 而不是你需要的,但是在这个问题中正在做你所要求的,我确实尝试过并打开 WinForm 然后在 5 后关闭秒,这里是代码:

      static void Main(string[] args)
      {
          MethodToRun();
      }
      
      private static async void MethodToRun()
      {
          var windowToOpen = new TestForm();
          var stringValue = String.Empty;
          Task.Run(new Action(() =>
          {
              Dispatcher.CurrentDispatcher.InvokeAsync(() =>
              {
                  windowToOpen.Show();
              }).Wait();
              System.Threading.Thread.Sleep(5000);
              stringValue = "Plop";
              Dispatcher.CurrentDispatcher.InvokeAsync(() =>
              {
                  if (String.Equals(stringValue, "Plop"))
                  {
                      windowToOpen.Close();
                  }
              }).Wait();
          })).Wait();
      }
      

      【讨论】:

      • 表单在五秒后打开和关闭,但 UI 仍然无响应
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-01-04
      • 1970-01-01
      • 1970-01-01
      • 2015-03-16
      • 1970-01-01
      • 1970-01-01
      • 2016-05-30
      相关资源
      最近更新 更多