Winforms 等待任务。如果存在System.Windows.Forms.Form实例,则延迟(…)将冻结

Winforms 等待任务。如果存在System.Windows.Forms.Form实例,则延迟(…)将冻结,winforms,task-parallel-library,async-await,c#-5.0,synchronizationcontext,Winforms,Task Parallel Library,Async Await,C# 5.0,Synchronizationcontext,以下程序挂起在DoTheStuff().Wait()上行,如果作为控制台应用程序运行: 名称空间测试 { 使用System.Threading.Tasks; 使用System.Windows.Forms; 班级计划 { 静态void Main(字符串[]参数) { 新形式(); DoTheStuff().Wait(); } 私有静态异步任务DoTheStuff() { 等待任务。延迟(1000); } } } 如果您注释掉新表单(),它的工作原理与预期一样行。(运行1秒,然后退出)。 如何保持

以下程序挂起在
DoTheStuff().Wait()上行,如果作为控制台应用程序运行:

名称空间测试
{
使用System.Threading.Tasks;
使用System.Windows.Forms;
班级计划
{
静态void Main(字符串[]参数)
{
新形式();
DoTheStuff().Wait();
}
私有静态异步任务DoTheStuff()
{
等待任务。延迟(1000);
}
}
}
如果您注释掉
新表单(),它的工作原理与预期一样行。(运行1秒,然后退出)。
如何保持预期的行为,并且仍然有一个表单实例

现在,如果您感兴趣,请提供一些背景资料:

我有一个作为windows服务托管的应用程序(在本地测试时作为控制台)

它需要访问
SystemEvents.TimeChanged
事件

但是,根据,这仅在具有windows窗体时有效(因此不在服务或控制台应用程序中)。链接文档中提供了一种解决方法,包括创建隐藏表单

不幸的是,该程序现在完全冻结,这是由wait和拥有
表单
实例的组合造成的

那么,在访问
SystemEvents.TimeChanged
事件时,我究竟如何才能保持预期的异步/等待行为呢


多亏了下面的帮助,这里有修改过的代码,可以不冻结地工作:

namespace Test
{
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    class Program
    {
        static void Main(string[] args)
        {
            new Thread(() => Application.Run(new Form())).Start();
            // SynchronizationContext.SetSynchronizationContext(null);
            DoTheStuff().Wait();
        }

        private static async Task DoTheStuff()
        {
            await Task.Delay(1000);
        }
    }
}
在我的程序中,我需要使用“SynchronizationContext.SetSynchronizationContext(null);”,因为线程池应该用于等待任务。我认为这不是一个好的实践,因为表单初始化它显然是有原因的。但是在没有用户输入的情况下隐藏运行表单(这是一项服务!),现在看不到任何危害


感觉有点不完整,MS甚至没有提到使用示例2可能出现的问题(wait/async在实例化表单时会隐式更改行为)。

这是出于设计。创建新表单对象获取Winforms管道以安装新的SynchronizationContext。您可以通过查看SynchronizationContext.Current属性在调试器中看到的内容

无论何时异步执行任何操作,该属性都是非常重要的。如果默认值为null,则使用wait获取要在线程池线程上运行的代码。如果不是,则等待管道将通过调用SynchronizationContext.Post()方法来实现等待。这样可以确保代码在主线程上运行

但这在你的程序中不起作用,因为你违反了合同。您没有调用Application.Run()。必需的


SystemEvents类将创建自己的隐藏通知窗口,如果您没有提供,则会启动一个消息循环。不需要创建表单。结果是它的事件将在任意线程上触发,而不是在主线程上触发。所以一定要注意锁定要求。

这是设计的。创建新表单对象获取Winforms管道以安装新的SynchronizationContext。您可以通过查看SynchronizationContext.Current属性在调试器中看到的内容

无论何时异步执行任何操作,该属性都是非常重要的。如果默认值为null,则使用wait获取要在线程池线程上运行的代码。如果不是,则等待管道将通过调用SynchronizationContext.Post()方法来实现等待。这样可以确保代码在主线程上运行

但这在你的程序中不起作用,因为你违反了合同。您没有调用Application.Run()。必需的


SystemEvents类将创建自己的隐藏通知窗口,如果您没有提供,则会启动一个消息循环。不需要创建表单。结果是它的事件将在任意线程上触发,而不是在主线程上触发。所以一定要注意锁定要求。

调用
Wait
会导致死锁,正如我解释和解释的那样

在您的情况下,可以使用简单的
mainascync

static void Main(string[] args)
{
    MainAsync().Wait();
}

static async Task MainAsync()
{
    new Form();
    await DoTheStuff();
}

private static async Task DoTheStuff()
{
    await Task.Delay(1000);
}
然而,这里有几个问题。首先,创建表单(甚至是隐藏表单)时,需要运行STA事件循环,例如,
Application.run
。其次,作为Win32服务,例如,
ServiceBase.Run


因此,我建议您使用一种解决方案,创建一个表单并在辅助线程上运行一个事件循环。

调用
Wait
会导致死锁,正如我所解释和介绍的

在您的情况下,可以使用简单的
mainascync

static void Main(string[] args)
{
    MainAsync().Wait();
}

static async Task MainAsync()
{
    new Form();
    await DoTheStuff();
}

private static async Task DoTheStuff()
{
    await Task.Delay(1000);
}
然而,这里有几个问题。首先,创建表单(甚至是隐藏表单)时,需要运行STA事件循环,例如,
Application.run
。其次,作为Win32服务,例如,
ServiceBase.Run


因此,我建议您在次线程上创建一个表单并运行一个事件循环的解决方案。

但是您是否尝试了
wait Task.Delay(1000).configurewait(false)
以确保您没有尝试重新输入捕获的上下文和死锁?但是您是否尝试了
wait Task.Delay(1000).configurewait(false)
确保您没有试图重新输入捕获的上下文和死锁?谢谢。这就解释了一切。但是“SystemEvents类将创建自己的隐藏通知窗口,如果您不提供,则会启动消息循环”中的错误。查看链接文档。在特定情况下,当作为服务运行时,这将不起作用,这就是我首先遇到问题的原因。那么,按照建议获得“在这种情况下,将自动提供消息循环”结果。谢谢。这就解释了一切。但是“SystemEvents类将创建自己的隐藏通知窗口,如果您不提供,则会启动消息循环”中的错误。看