gpt4 book ai didi

winforms - WinForms 应用程序中的异步任务 Main() 具有异步初始化

转载 作者:行者123 更新时间:2023-12-02 22:05:25 24 4
gpt4 key购买 nike

我们有一个使用异步初始化过程的 winforms 应用程序。简单地说,应用程序将运行以下步骤:

  • 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 here 非常详细地描述了其工作原理的背景。 .

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

但这行不通。原因很清楚。第一个 await 之后的所有代码都将被转发到消息循环。但消息循环尚未启动,因为启动它的代码 (Application.Run()) 位于第一个 await 之后。

删除同步上下文将解决问题,但会导致在不同线程中 await 之后运行代码。

重新排序代码以在第一个 await 之前调用 Application.Run() 将不起作用,因为它是阻塞调用。

我们尝试使用异步任务 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 抛出(或消息循环抛出的其他任何内容),它仍然可以工作。异常将在包装 Application.Runtry/catch 内捕获。它只是一个 TargetInvocationException 异常,原始异常可通过其 InnerException 属性获得。

更新以解决评论:

But for me it looks very strange to register an EventHandler to the idle event so startup the whole application. It's totally clear how that works but still strange. In that case I prefer the HandleException solution that I already have.

我想这是一个品味问题。我不知道为什么WinForms API设计者没有提供像WPF的Application.Startup这样的东西。然而,由于 WinForm 的 Application 类上缺乏专门的事件,因此在第一个 Idle 事件上推迟特定的初始化代码在我看来是一个优雅的解决方案,并且在这里广泛使用就这样。

我特别不喜欢在 Application.Run 启动之前显式手动配置 WindowsFormsSynchronizationContext,但如果您需要替代解决方案,给你:

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

IMO,这两种方法都比您问题中使用的方法更干净。顺便说一句,您应该使用 ApplicationContext处理表单关闭。您可以将 ApplicationContext 的实例传递给 Application.Run

The only point that I'm missing is your hint that the synchronization context is already set. Yes it is - but why?

它确实被设置为 Application.Run 的一部分(如果当前线程上尚未存在)。如果您想了解更多详细信息,可以在.NET Reference Source中进行调查。 。

关于winforms - WinForms 应用程序中的异步任务 Main() 具有异步初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49902641/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com