有没有办法为 c# TPL 中的任务执行某种 TryStart(无锁定)?
Is there a way to do a kind of TryStart for a task in c# TPL (without locking)?
这样,如果任务还没有被另一个线程启动,任务就会启动。
例如
private readonly object syncObj = new object();
private Task<Resource> task;
public Resource GetResource()
{
lock (syncObj)
{
if (task == null)
task = Task.Factory.StartNew<Resource>(CreateResource);
}
task.Wait();
return task.Result;
}
private Resource CreateResource()
{
//do something
}
有没有办法在根本不使用 lock
的情况下重写 GetResource()
以使其保持线程安全?
如果我理解正确的话,您是在尝试懒惰地创建一些资源。您可以使用 AsyncLazy
来做到这一点。此外,由于您已经阻止了任务,因此您可以只使用常规 Lazy
:
Lazy<Resource> _lazy = new Lazy<Resource>(CreateResource);
public Resource GetResource()
{
return _lazy.Value;
}
而是直接回答你的问题。如果你想删除锁并在不同的线程上仍然有 CreateResource
运行 你可以使用 TaskCompletionSource
和 Interlocked.CompareExchange
:
public Resource GetResource()
{
var tcs = new TaskCompletionSource<Resource>();
var storedTask = Interlocked.CompareExchange(ref task, tcs.Task, null);
if (storedTask != null)
{
return storedTask.Result;
}
var resource = Task.Factory.StartNew<Resource>(CreateResource).Result;
tcs.SetResult(resource);
return resource;
}
这只会将 task
设置为 tcs.Task
,如果它包含 null
和 returns task
的原始值。这样我们就可以只在第一次启动 CreateResource
任务(task == null
)并用结果完成存储的 task
。
如果你让它异步而不是阻塞,这会变得更简单:
async Task<Resource> GetResourceAsync()
{
var tcs = new TaskCompletionSource<Resource>();
var storedTask = Interlocked.CompareExchange(ref task, tcs.Task, null);
if (storedTask != null)
{
return await storedTask;
}
var resource = await Task.Run(() => CreateResource());
tcs.SetResult(resource);
return resource;
}
我会像@i3arnon 所说的那样使用 AsyncLazy<T>
,或者公开 Task<Resource>
而不是同步阻塞它:
public Task<Resource> GetResourceAsync()
{
lock (syncObj)
{
if (task == null)
task = Task.Run(() => CreateResource);
}
return task;
}
这样,你让调用者决定他是想同步还是异步等待结果。
考虑时,我可能会expose this as a synchronous operation instead,让调用者决定是否应该在后台线程上执行:
private readonly Lazy<Resource> resource = new Lazy<Resource>(() => CreateResource(), true);
public Resource GetResource()
{
return resource.Value;
}
这样,如果任务还没有被另一个线程启动,任务就会启动。
例如
private readonly object syncObj = new object();
private Task<Resource> task;
public Resource GetResource()
{
lock (syncObj)
{
if (task == null)
task = Task.Factory.StartNew<Resource>(CreateResource);
}
task.Wait();
return task.Result;
}
private Resource CreateResource()
{
//do something
}
有没有办法在根本不使用 lock
的情况下重写 GetResource()
以使其保持线程安全?
如果我理解正确的话,您是在尝试懒惰地创建一些资源。您可以使用 AsyncLazy
来做到这一点。此外,由于您已经阻止了任务,因此您可以只使用常规 Lazy
:
Lazy<Resource> _lazy = new Lazy<Resource>(CreateResource);
public Resource GetResource()
{
return _lazy.Value;
}
而是直接回答你的问题。如果你想删除锁并在不同的线程上仍然有 CreateResource
运行 你可以使用 TaskCompletionSource
和 Interlocked.CompareExchange
:
public Resource GetResource()
{
var tcs = new TaskCompletionSource<Resource>();
var storedTask = Interlocked.CompareExchange(ref task, tcs.Task, null);
if (storedTask != null)
{
return storedTask.Result;
}
var resource = Task.Factory.StartNew<Resource>(CreateResource).Result;
tcs.SetResult(resource);
return resource;
}
这只会将 task
设置为 tcs.Task
,如果它包含 null
和 returns task
的原始值。这样我们就可以只在第一次启动 CreateResource
任务(task == null
)并用结果完成存储的 task
。
如果你让它异步而不是阻塞,这会变得更简单:
async Task<Resource> GetResourceAsync()
{
var tcs = new TaskCompletionSource<Resource>();
var storedTask = Interlocked.CompareExchange(ref task, tcs.Task, null);
if (storedTask != null)
{
return await storedTask;
}
var resource = await Task.Run(() => CreateResource());
tcs.SetResult(resource);
return resource;
}
我会像@i3arnon 所说的那样使用 AsyncLazy<T>
,或者公开 Task<Resource>
而不是同步阻塞它:
public Task<Resource> GetResourceAsync()
{
lock (syncObj)
{
if (task == null)
task = Task.Run(() => CreateResource);
}
return task;
}
这样,你让调用者决定他是想同步还是异步等待结果。
考虑时,我可能会expose this as a synchronous operation instead,让调用者决定是否应该在后台线程上执行:
private readonly Lazy<Resource> resource = new Lazy<Resource>(() => CreateResource(), true);
public Resource GetResource()
{
return resource.Value;
}