使用 JoinableTaskFactory.Run 优化构造函数中的异步调用

Optimising an asynchronous call in a constructor using JoinableTaskFactory.Run

在注入依赖项的构造函数中,我需要进行异步调用。

(这最初不是我的代码。它在很多地方使用。我认为它不相关,但这是 FluentValidation.AbstractValidator<T> 实现的一部分。ConfigureValidationRulesAsync() 的实现在基地 class.)

public class SampleClass<TModel> : SampleBase<TModel>, ISampleClass
{
    public SampleClass()
    {
        // ... RuleFor(...);

        var genericInstance = Activator.CreateInstance<TModel>();
        Task.Run(() => ConfigureValidationRulesAsync(this, genericInstance)).Wait();
    }
}

这给了我以下警告。

VSTHRD002

Synchronously waiting on tasks or awaiters may cause deadlocks. Use await or JoinableTaskFactory.Run instead.

我关注的...

public class SampleClass<TModel> : SampleBase<TModel>, ISampleClass
{
    public SampleClass()
    {
        // ... RuleFor(...);

        // Using "JoinableTaskFactory()" directly because "ThreadHelper.JoinableTaskFactory" is .NET Framework, not .NET 6.

        var genericInstance = Activator.CreateInstance<TModel>();
        var joinable = new JoinableTaskFactory(new JoinableTaskContext());
        joinable.Run(async () => await ConfigureValidationRulesAsync(this, genericInstance));
    }
}

但现在我收到了一个新建议(至少不是警告)。

VSTHRD104

Expose an async version of this method that does not synchronously block. Then simplify this method to call that async method within a JoinableTaskFactory.Run delegate.

根据 this documentation,这是我的第一次(失败的)尝试,未能使建议消失。

public class SampleClass<TModel> : SampleBase<TModel>, ISampleClass
{
    public SampleClass()
    {
        var joinable = new JoinableTaskFactory(new JoinableTaskContext());
        joinable.Run(async () => await SampleClassAsync());
    }

    public async Task SampleClassAsync()
    {
        // ... RuleFor(...);

        var genericInstance = Activator.CreateInstance<TModel>();
        await ConfigureValidationRulesAsync(this, genericInstance);
    }
}

我的第二次尝试解决了该建议,但触发了不同的建议。

public class SampleClass<TModel> : SampleBase<TModel>, ISampleClass
{
    public SampleClass()
    {
        ConfigureRules();
    }

    private void ConfigureRules()
    {
        var joinable = new JoinableTaskFactory(new JoinableTaskContext());
        joinable.Run(async () => await ConfigureRulesAsync());
    }

    private async Task ConfigureRulesAsync()
    {
        // ... RuleFor(...);

        var genericInstance = Activator.CreateInstance<TModel>();
        await ConfigureValidationRulesAsync(this, genericInstance);
    }
}

VSTHRD102

Limit use of synchronously blocking method calls such as JoinableTaskFactory.Run or Task.Result to public entrypoint members where you must be synchronous. Using it for internal members can needlessly add synchronous frames between asynchronous frames, leading to threadpool exhaustion.

是否可以解决这些针对依赖注入构造函数的建议?或者我应该忽略 JoinableTaskFactory.Run 建议并将我的代码保留在构造函数中?

以下是我从对该问题的所有评论中得出的最佳结论。

第 1 点:注入构造函数应该简单

Injection constructors should be simple, and in the context of Fluent Validation, the MustAsync 方法应该有助于简化异步验证。

第 2 点:警告不适用于 .NET Core

虽然我可以使用 JoinableTaskFactory,但我不应该收到此警告,因为它试图解决的问题不适用于 .NET Core。

以下是来自 documentation for JoinableTaskFactory 的描述。它适用于将线程加入主线程可能导致死锁的情况。

A factory for starting asynchronous tasks that can mitigate deadlocks when the tasks require the Main thread of an application and the Main thread may itself be blocking on the completion of a task.

因此,在无法重写代码的情况下,可以安全地忽略警告。 (注意 Task.Run() 的用法,即 the recommended way to launch a compute-bound task as of .NET Framework 4.5。)

#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits
        Task.Run(() => MethodAsync()).Wait();
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits

第 3 点:如何实施 JoinableTaskFactory

如果你想使用 JoinableTaskFactory,这是从 上的评论中摘录的。

对于 .NET Framework 项目,使用 ThreadHelper.JoinableTaskFactory

对于.NET Core项目,如果要使用JoinableTaskFactory,不要每次使用都新建context。您应该创建一个共享 JoinableTaskContext 对象并在整个应用程序中使用该对象(每个请求或每个进程)。 SynchronizationContext.Current == null.

时就是这种情况

第 4 点:Visual Studio 警告

警告来自 NuGet 包 Microsoft.VisualStudio.Threading.Analyzers。保持安装它很有用,因为当您使用 MethodAsync().ResultMethodAsync().Wait() 之类的代码时,它也会警告您,而您应该使用 await MethodAsync().