1

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

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

    class Program
    {
        static void Main(string[] args)
        {
            new Form();
            DoTheStuff().Wait();
        }

        private static async Task DoTheStuff()
        {
            await Task.Delay(1000);
        }
    }
}

如果您注释掉该new Form();行,它会按预期工作。(运行 1 秒,然后退出)。 我怎样才能保持预期的行为并且仍然有一个 Form 实例?

现在,如果您有兴趣,请提供一些背景知识:

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

它需要有权访问该SystemEvents.TimeChanged事件。

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

不幸的是,程序现在完全冻结了,这是由 await 和拥有Form实例的组合引起的。

那么在访问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);”,因为线程池应该用于等待任务。我认为这不是一个好习惯,因为 Form 显然是出于某种原因对其进行了初始化。但是在没有用户输入的情况下运行隐藏的表单(它是一项服务!),现在看不到任何伤害。

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

4

2 回答 2

4

这是设计使然。创建一个新的 Form 对象让 Winforms 管道安装一个新的 SynchronizationContext。通过查看 SynchronizationContext.Current 属性,您可以在调试器中看到一些内容。

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

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

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

于 2013-04-29T12:05:47.813 回答
1

正如我在我的博客最近的 MSDN 文章中所解释的那样,调用会Wait导致死锁。

在您的情况下,您可以使用MainAsync这样的简单方法:

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 服务,您应该将主线程返回给 SCM,例如ServiceBase.Run.

所以我会推荐一个解决方案,您可以创建一个表单并在辅助线程上运行一个事件循环。

于 2013-04-29T12:08:43.637 回答