首页 > 解决方案 > 异步,在 C# 中等待

问题描述

在关闭我的应用程序时,我必须做一些清理活动,我在 ClassA.cs 中写了类似的东西

protected override void OnExit(ExitEventArgs e)
{
    Cleanup();
    base.OnExit(e);
}

private async Task Cleanup()
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (databaseInitialisation != null)
    {
        await databaseInitialisation.InitialiseDatabase();
    }
     databaseFile?.Dispose();
}

我还有一堂 ClassB.cs

public class DatabaseInitialisation : IDatabaseInitialisation
{
    private readonly string filterOptimisationPath;
    private readonly Task databaseInitialisation;

    private IDbConnectionFactory ConnectionFactory { get; }
    public async Task InitialiseDatabase() => await databaseInitialisation;

    public DatabaseInitialisation(string databaseFilePath, string filterOptimisationPath)
    {
        this.filterOptimisationPath = filterOptimisationPath;
        ConnectionFactory = new SqLiteConnectionFactory(databaseFilePath);
        databaseInitialisation = Task.Run(() => CreateDatabase(databaseFilePath));
    }
}

所以现在当我关闭我的应用程序时,有时当调用清理时,从等待开始的程序执行将转到 onexit 方法而不执行 dispose 方法

标签: c#async-await

解决方案


问题的根源在于您需要async从同步方法调用一个方法,而您无法创建该方法,async因为它是 GUI 框架的一部分。这里的一般建议是让你的调用方法async,但既然你不能,有几种方法可以做到这一点:

选项1 - Task.Run

更改您的OnExit方法以Cleanup在线程池线程上启动,然后同步等待使用GetAwaiter().GetResult()以防止死锁。由于这仅在关闭时运行一次,应该没问题,但是如果这是一种经常运行的方法,则不建议启动这样的后台线程,因为它可能导致线程池饥饿。

protected override void OnExit(ExitEventArgs e)
{
    Task.Run(async () => await Cleanup().ConfigureAwait(false))
        .GetAwaiter().GetResult();
    base.OnExit(e);
}

选项 2 -async void和一个ManualResetEvent

这种方法避免.GetAwaiter().GetResult()了在线程池线程上使用和启动,但是在清理中抛出的任何异常都会导致应用程序崩溃。

protected override void OnExit(ExitEventArgs e)
{
    using var signal = new ManualResetEventSlim(false);
    Cleanup(signal);
    signal.Wait();
    base.OnExit(e);
}

private async void Cleanup(ManualResetEventSlim signal)
{
    if (databaseInitialisation != null)
    {
        await databaseInitialisation.InitialiseDatabase();
    }
     databaseFile?.Dispose();
    signal.Set();
}

您还可以使用其中一种Wait重载来包含超时。

附带说明一下,在这样的 GUI 应用程序中,您确实应该调用.ConfigureAWait(false)您等待的每个不是直接从 GUI 事件调用的任务。不这样做会导致死锁。

这两种解决方案的一个警告是,如果您的 Cleanup 方法或其调用堆栈中的任何内容,这两种方法都会死锁,在这种情况下,需要另一种解决方案。


推荐阅读