C# 强制一个异步方法被调用一次
假设我有一个类需要使用InitializeAsync()方法执行一些异步初始化。 我想确保初始化只执行一次。如果另一个线程在初始化过程中调用此方法,它将“等待”直到第一个调用返回 我正在考虑以下实现(使用信号量lim)。 是否有更好/更简单的方法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
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
上下文中调用。无法在当前上下文中调用它
await asyncLazy
后面跟着一些冗长/阻塞的代码,则等待相同asyncLazy
的所有其他异步工作流都将受到影响(延迟)。此问题特别影响.NET Framework。对于.NET核心和.NET 5来说,这不是一个问题,因为在这些平台上,异步生成的(不完整的)任务的延续是异步运行的。这不是一个严重性很高的问题,因为它只影响在完成之前等待AsyncLazy
的工作流
Lazy
组合。在某些情况下,这种组合似乎是可行的
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;