WinForms应用程序中具有异步初始化的异步任务Main()
我们有一个winforms应用程序,它使用异步初始化过程。您可以说应用程序将运行以下步骤:WinForms应用程序中具有异步初始化的异步任务Main(),winforms,async-await,main,c#-7.1,Winforms,Async Await,Main,C# 7.1,我们有一个winforms应用程序,它使用异步初始化过程。您可以说应用程序将运行以下步骤: Init-这是异步运行的 显示主窗体 Application.Run() 当前存在的工作代码如下所示: [STAThread] private static void Main() { SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext()); var task
- Init-这是异步运行的
- 显示主窗体
- Application.Run()
[STAThread]
private static void Main()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
var task = StartUp();
HandleException(task);
Application.Run();
}
private static async Task StartUp()
{
await InitAsync();
var frm = new Form();
frm.Closed += (_, __) => Application.ExitThread();
frm.Show();
}
private static async Task InitAsync()
{
// the real content doesn't matter
await Task.Delay(1000);
}
private static async void HandleException(Task task)
{
try
{
await Task.Yield();
await task;
}
catch (Exception e)
{
Console.WriteLine(e);
Application.ExitThread();
}
}
马克·索乌尔(Mark Sowul)详细描述了这项工作的背景
从C#7.1开始,我们可以在main方法中使用异步任务。我们以直截了当的方式进行了尝试:
[STAThread]
private static async Task Main()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
try
{
await StartUp();
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
Application.ExitThread();
}
}
private static async Task StartUp()
{
await InitAsync();
var frm = new Form();
frm.Closed += (_, __) => Application.ExitThread();
frm.Show();
}
private static async Task InitAsync()
{
// the real content doesn't matter
await Task.Delay(1000);
}
但这不起作用。原因很清楚。第一次wait
之后的所有代码都将转发到消息循环。但是消息循环尚未启动,因为启动它的代码(Application.Run()
)位于第一个wait
之后
删除同步上下文将修复该问题,但会导致在另一个线程中运行wait
之后的代码
在第一个wait
调用之前重新排序代码以调用Application.Run()
将不起作用,因为这是一个阻塞调用
我们尝试使用一个新功能,即拥有一个async Task Main()
,它允许我们删除难以理解的HandleException
——解决方案。但我们不知道怎么做
您有什么建议吗?您不需要
async Main
。以下是如何做到这一点:
[STAThread]
static void Main()
{
void threadExceptionHandler(object s, System.Threading.ThreadExceptionEventArgs e)
{
Console.WriteLine(e);
Application.ExitThread();
}
async void startupHandler(object s, EventArgs e)
{
// WindowsFormsSynchronizationContext is already set here
Application.Idle -= startupHandler;
try
{
await StartUp();
}
catch (Exception)
{
// handle if desired, otherwise threadExceptionHandler will handle it
throw;
}
};
Application.ThreadException += threadExceptionHandler;
Application.Idle += startupHandler;
try
{
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
Application.Idle -= startupHandler;
Application.ThreadException -= threadExceptionHandler;
}
}
注意,如果您没有注册threadExceptionHandler
事件处理程序和StartUp
抛出(或者消息循环抛出的任何其他内容),它仍然可以工作。异常将在包装应用程序的try/catch
中捕获。运行。它将只是一个targetingException
异常,原始异常通过其InnerException
属性可用
更新了以回应评论:
但对我来说,将EventHandler注册到
空闲事件,以便启动整个应用程序。很清楚怎么做
这是可行的,但仍然很奇怪。那样的话,我更喜欢
HandleException解决方案,我已经有了
我想这是品味的问题。我不知道为什么WinFormsAPI设计者不提供类似WPF的东西。但是,由于WinForm的应用程序
类上没有专门的事件,因此在第一个空闲
事件时延迟特定的初始化代码是一个优雅的解决方案,在这里广泛使用
我特别不喜欢在应用程序启动之前显式手动设置WindowsFormsSynchronizationContext
。Run
,但如果您需要其他解决方案,请执行以下操作:
[STAThread]
static void Main()
{
async void startupHandler(object s)
{
try
{
await StartUp();
}
catch (Exception ex)
{
// handle here if desired,
// otherwise it be asynchronously propogated to
// the try/catch wrapping Application.Run
throw;
}
};
// don't dispatch exceptions to Application.ThreadException
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
using (var ctx = new WindowsFormsSynchronizationContext())
{
System.Threading.SynchronizationContext.SetSynchronizationContext(ctx);
try
{
ctx.Post(startupHandler, null);
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
System.Threading.SynchronizationContext.SetSynchronizationContext(null);
}
}
}
在国际海事组织,这两种方法都比你在问题中使用的方法更干净。另一方面,您应该使用来处理表单闭包。您可以将ApplicationContext
的实例传递给Application.Run
我唯一想说的是
缺少同步上下文已设置的提示。
是的,但是为什么呢
它确实被设置为应用程序的一部分。如果当前线程上尚未出现,请运行。如果您想了解更多详细信息,可以在中进行调查 谢谢你的回答。我测试了它,当然,它是有效的。但对我来说,将EventHandler注册到空闲事件中以便启动整个应用程序看起来非常奇怪。这是完全清楚的工作原理,但仍然奇怪。在这种情况下,我更喜欢已有的HandleException
解决方案。我唯一遗漏的一点是,您提示已经设置了同步上下文。是的,但是为什么呢?我知道它是在创建第一个控件时初始化的。也许Application.Run()
也会对其进行初始化。顺便说一句,我们也遇到了类似的问题,但我们根本不想调用Application.Run()(对于具有一些奇怪约束的混合CLI/GUI应用程序…)。多亏了您的回答,我们解决了这个问题。我们将异步代码包装到AsyncContext.Run(…)(Stephen Cleary的AsyncContext)