有没有办法为 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 运行 你可以使用 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;
}

这只会将 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;
}