C# 强制一个异步方法被调用一次

C# 强制一个异步方法被调用一次,c#,.net,asynchronous,async-await,task-parallel-library,C#,.net,Asynchronous,Async Await,Task Parallel Library,假设我有一个类需要使用InitializeAsync()方法执行一些异步初始化。 我想确保初始化只执行一次。如果另一个线程在初始化过程中调用此方法,它将“等待”直到第一个调用返回 我正在考虑以下实现(使用信号量lim)。 是否有更好/更简单的方法 public class MyService : IMyService { private readonly SemaphoreSlim mSemaphore = new SemaphoreSlim(1, 1); private boo

假设我有一个类需要使用InitializeAsync()方法执行一些异步初始化。 我想确保初始化只执行一次。如果另一个线程在初始化过程中调用此方法,它将“等待”直到第一个调用返回

我正在考虑以下实现(使用信号量lim)。 是否有更好/更简单的方法

public class MyService : IMyService
{
    private readonly SemaphoreSlim mSemaphore = new SemaphoreSlim(1, 1);
    private bool mIsInitialized;

    public async Task InitializeAsync()
    {
        if (!mIsInitialized)
        {
            await mSemaphore.WaitAsync();

            if (!mIsInitialized)
            {
                await DoStuffOnlyOnceAsync();
                mIsInitialized = true;
            }

            mSemaphore.Release();
        }
    }

    private Task DoStuffOnlyOnceAsync()
    {
        return Task.Run(() =>
        {
            Thread.Sleep(10000);
        });
    }
}
谢谢

编辑:

由于我使用的是DI,并且该服务将被注入,因此将其作为“惰性”资源使用或使用异步工厂对我来说是不起作用的(尽管在其他用例中它可能非常有用)。 因此,异步初始化应该封装在类中,并对
IMyService
使用者透明

将初始化代码包装在一个“虚拟”
AsyncLazy
对象中的想法可以完成这项工作,尽管我觉得有点不自然。

我同意(稍微修改的版本):

公共类异步延迟:延迟
{ 
公共值(Func valueFactory):
基本(()=>Task.Run(valueFactory)){}
公共异步(Func任务工厂):
基本(()=>Task.Run(()=>taskFactory()){}
公共任务等待器GetAwaiter(){return Value.GetAwaiter();}
}
然后像这样消费:

private AsyncLazy<bool> asyncLazy = new AsyncLazy<bool>(async () =>
                                    { 
                                        await DoStuffOnlyOnceAsync()
                                        return true;
                                    });
private AsyncLazy AsyncLazy=new AsyncLazy(async()=>
{ 
等待DoStuffOnlyOnceAsync()
返回true;
});
注意,我之所以使用
bool
,只是因为您没有来自
DoStuffOnlyOnceAsync
的返回类型

编辑:

Stephan Cleary(当然)也实现了这一点。

是的。使用(可从以下网站获得):

private static readonly AsyncLazy myResource=new AsyncLazy(
异步()=>
{ 
var ret=new MyResource();
等待ret.InitAsync();
返回ret;
}
);
公共异步任务UseResource()
{
MyResource资源=等待MyResource;
// ...
}
如果您喜欢Microsoft实施,也可以使用。我有一个

通常,我更喜欢异步工厂方法,因为我认为它们更简单、更安全:

public class MyService
{
  private MyService() { }

  public static async Task<MyService> CreateAsync()
  {
    var result = new MyService();
    result.Value = await ...;
    return result;
  }
}
公共类MyService
{
私有MyService(){}
公共静态异步任务CreateAsync()
{
var result=new MyService();
结果。值=等待。。。;
返回结果;
}
}
AsyncLazy
是定义共享异步资源的一种非常好的方法(并且可能是“服务”更好的概念匹配,具体取决于它的使用方式)。异步工厂方法方法的一个优点是,不可能创建未初始化版本的
MyService

Stephen Toub的实现非常好且简洁,但有两件事我不喜欢:

  • 如果异步操作失败,错误将被缓存,并将传播到
    AsyncLazy
    实例的所有未来等待者。无法取消缓存已缓存的
    任务
    ,因此可以重试异步操作

  • 异步委托在
    ThreadPool
    上下文中调用。无法在当前上下文中调用它

  • 当异步委托完成时,如果附加了多个continuations,则将在同一线程上同步调用所有continuations。因此,如果一个异步工作流的
    await asyncLazy
    后面跟着一些冗长/阻塞的代码,则等待相同
    asyncLazy
    的所有其他异步工作流都将受到影响(延迟)。此问题特别影响.NET Framework。对于.NET核心和.NET 5来说,这不是一个问题,因为在这些平台上,异步生成的(不完整的)任务的延续是异步运行的。这不是一个严重性很高的问题,因为它只影响
    在完成之前等待
    AsyncLazy
    的工作流

  • 最新版本的VisualStudio2019(16.8.2)中生成了
    Lazy
    组合。在某些情况下,这种组合似乎是可行的

  • Stephen Cleary的
    AsyncLazy
    (库的一部分)解决了第一个问题,它在其构造函数中接受
    RetryOnFailure
    标志。第二个问题也通过相同的实现(
    executeonCallingRead
    flag)得到了解决,但不是以最佳方式。如果异步委托包含阻止代码,则同时访问
    AsyncLazy.Task
    属性的所有工作流都将被阻止。这是该类工作方式的直接结果。AFAIK第三和第四个问题尚未解决

    下面是解决所有这些问题的尝试。此实现不是基于
    惰性
    ,而是在内部使用临时嵌套任务(
    任务
    )作为包装器

    /// <summary>
    /// Represents a single asynchronous operation that is started on first demand.
    /// In case of failure the error is not cached, and the operation is restarted
    /// (retried) later on demand.
    /// </summary>
    public class AsyncLazy<TResult>
    {
        private Func<Task<TResult>> _factory;
        private Task<TResult> _task;
    
        public AsyncLazy(Func<Task<TResult>> factory)
        {
            _factory = factory ?? throw new ArgumentNullException(nameof(factory));
        }
    
        public Task<TResult> Task
        {
            get
            {
                var currentTask = Volatile.Read(ref _task);
                if (currentTask == null)
                {
                    Task<TResult> newTask = null;
                    var newTaskTask = new Task<Task<TResult>>(async () =>
                    {
                        try
                        {
                            var result = await _factory().ConfigureAwait(false);
                            _factory = null; // No longer needed (let it get recycled)
                            return result;
                        }
                        catch
                        {
                            _ = Interlocked.CompareExchange(ref _task, null, newTask);
                            throw;
                        }
                    });
                    newTask = newTaskTask.Unwrap();
                    currentTask = Interlocked
                        .CompareExchange(ref _task, newTask, null) ?? newTask;
                    if (currentTask == newTask)
                        newTaskTask.RunSynchronously(TaskScheduler.Default);
                }
                return currentTask.IsCompleted ?
                    currentTask : RunContinuationsAsynchronously(currentTask);
            }
        }
    
        public TaskAwaiter<TResult> GetAwaiter() { return this.Task.GetAwaiter(); }
    
        private static Task<TResult> RunContinuationsAsynchronously(Task<TResult> task)
        {
            return task.ContinueWith(t => t,
                default, TaskContinuationOptions.RunContinuationsAsynchronously,
                TaskScheduler.Default).Unwrap();
        }
    }
    
    //
    ///表示第一次请求时启动的单个异步操作。
    ///如果出现故障,则不会缓存错误,并重新启动操作
    ///(重试)稍后按要求执行。
    /// 
    公共类异步
    {
    私营Func_工厂;
    私人任务(u Task),;
    公共异步(Func工厂)
    {
    _工厂=工厂??抛出新的ArgumentNullException(工厂名称));
    }
    公共任务
    {
    得到
    {
    var currentTask=Volatile.Read(参考任务);
    如果(currentTask==null)
    {
    任务newTask=null;
    var newTaskTask=新任务(异步()=>
    {
    尝试
    {
    var result=await _factory().ConfigureAwait(false);
    _factory=null;//不再需要(设为g
    
    public class MyService
    {
      private MyService() { }
    
      public static async Task<MyService> CreateAsync()
      {
        var result = new MyService();
        result.Value = await ...;
        return result;
      }
    }
    
    /// <summary>
    /// Represents a single asynchronous operation that is started on first demand.
    /// In case of failure the error is not cached, and the operation is restarted
    /// (retried) later on demand.
    /// </summary>
    public class AsyncLazy<TResult>
    {
        private Func<Task<TResult>> _factory;
        private Task<TResult> _task;
    
        public AsyncLazy(Func<Task<TResult>> factory)
        {
            _factory = factory ?? throw new ArgumentNullException(nameof(factory));
        }
    
        public Task<TResult> Task
        {
            get
            {
                var currentTask = Volatile.Read(ref _task);
                if (currentTask == null)
                {
                    Task<TResult> newTask = null;
                    var newTaskTask = new Task<Task<TResult>>(async () =>
                    {
                        try
                        {
                            var result = await _factory().ConfigureAwait(false);
                            _factory = null; // No longer needed (let it get recycled)
                            return result;
                        }
                        catch
                        {
                            _ = Interlocked.CompareExchange(ref _task, null, newTask);
                            throw;
                        }
                    });
                    newTask = newTaskTask.Unwrap();
                    currentTask = Interlocked
                        .CompareExchange(ref _task, newTask, null) ?? newTask;
                    if (currentTask == newTask)
                        newTaskTask.RunSynchronously(TaskScheduler.Default);
                }
                return currentTask.IsCompleted ?
                    currentTask : RunContinuationsAsynchronously(currentTask);
            }
        }
    
        public TaskAwaiter<TResult> GetAwaiter() { return this.Task.GetAwaiter(); }
    
        private static Task<TResult> RunContinuationsAsynchronously(Task<TResult> task)
        {
            return task.ContinueWith(t => t,
                default, TaskContinuationOptions.RunContinuationsAsynchronously,
                TaskScheduler.Default).Unwrap();
        }
    }
    
    var deferredTask = new AsyncLazy<string>(async () =>
    {
        return await _httpClient.GetStringAsync("https://stackoverflow.com");
    });
    
    //... (the operation has not started yet)
    
    string html = await deferredTask;