C# 异步/等待在单线程上运行

C# 异步/等待在单线程上运行,c#,multithreading,task,C#,Multithreading,Task,我有一个控制台应用程序,在某些情况下需要显示用户界面。此用户界面需要保持响应,因为它将包含加载gif、进度条、取消按钮等。我有以下示例代码: class Program { static void Main(string[] args) { DoWork().GetAwaiter().GetResult(); } private static async Task DoWork() { TestForm form = ne

我有一个控制台应用程序,在某些情况下需要显示用户界面。此用户界面需要保持响应,因为它将包含加载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();
        }
    }
}

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

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

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

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();
        }
    }

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

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

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();
        }
    }

好的,我确实标记了要删除的第一个答案,因为我放在那里的内容适用于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();
}

好的,我确实标记了要删除的第一个答案,因为我放在那里的内容适用于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();
}

因此,我设法为这个问题找到了一个肮脏的解决方案。这不是一个干净的解决方案,所以我仍然愿意接受建议,但对于我需要的东西,它工作得很好

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");
    }
private static void DoWork()
{
TestForm form=新的TestForm();
Task formTask=Task.Run(()=>form.ShowDialog());
Task testTask=Task.Run(()=>
{
对于(int i=1;i<10;i++)
{
睡眠(1000);
Console.WriteLine(i.ToString());
}
Console.WriteLine(“后台任务完成”);
返回“扑通一声”;
});
Console.WriteLine(“等待后台任务”);
testTask.Wait();
if(testTask.Result==“Plop”)
{
Dispatcher.CurrentDispatcher.InvokeAsync(()=>form.Close());
}
Console.WriteLine(“应用程序完成”);
}

这将首先输出“等待后台任务”,然后是任务的计数,然后在长流程完成时输出“后台任务完成”,并关闭响应的UI表单,因此我成功地为这项任务拼凑了一个肮脏的解决方案。这不是一个干净的解决方案,所以我仍然愿意接受建议,但对于我需要的东西,它工作得很好

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");
    }
private static void DoWork()
{
TestForm form=新的TestForm();
Task formTask=Task.Run(()=>form.ShowDialog());
Task testTask=Task.Run(()=>
{
对于(int i=1;i<10;i++)
{
睡眠(1000);
Console.WriteLine(i.ToString());
}
Console.WriteLine(“后台任务完成”);
返回“扑通一声”;
});
Console.WriteLine(“等待后台任务”);
testTask.Wait();
if(testTask.Result==“Plop”)
{
Dispatcher.CurrentDispatcher.InvokeAsync(()=>form.Close());
}
Console.WriteLine(“应用程序完成”);
}

这将首先输出“等待后台任务”,然后是任务的计数,然后在长流程完成时输出“后台任务完成”,并关闭响应的UI表单

这是WinForms还是console应用程序?似乎您需要在某个地方启动事件泵,例如,
应用程序。运行(表单)
。据我所知,无法保证任务将在与UI线程分离的线程上运行,您不应该使用线程。在任务中睡眠,因为它是一个阻塞操作@这是一个控制台application@AlphaDelta当我替换Thread.Sleep和for-int循环时,我看到了相同的结果,因此您有一个控制台应用程序,它提供了