是否有一种方法可以在c# TPL中为任务做一种TryStart(不加锁)?

本文关键字:一种 任务 TryStart 加锁 方法 是否 TPL | 更新日期: 2023-09-27 18:18:54

这样,如果Task还没有被其他线程启动,Task就会启动。

例如

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
}

是否有一种方法可以重写GetResource()而不使用lock,因此它仍然是线程安全的?

是否有一种方法可以在c# TPL中为任务做一种TryStart(不加锁)?

如果我理解正确,您正在尝试惰性创建一些资源。你可以用AsyncLazy来做。此外,由于您已经阻塞了任务,您可以使用常规的Lazy:

Lazy<Resource> _lazy = new Lazy<Resource>(CreateResource);
public Resource GetResource()
{
    return _lazy.Value;
}

而是直接回答你的问题。如果你想要移除锁并且仍然让CreateResource在另一个线程上运行,你可以使用TaskCompletionSourceInterlocked.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;
}

如果自动包含null,则将task设置为tcs.Task,并返回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;
}
通过这种方式,您可以让调用者决定是否要同步或异步等待结果。

考虑到这一点,我可能会将其公开为同步操作,并让调用者决定是否应该在后台线程中执行:

private readonly Lazy<Resource> resource = new Lazy<Resource>(() => CreateResource(), true);
public Resource GetResource()
{
      return resource.Value;
}