Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/magento/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
WinForms应用程序中具有异步初始化的异步任务Main()_Winforms_Async Await_Main_C# 7.1 - Fatal编程技术网

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

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

我们有一个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)详细描述了这项工作的背景

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