【问题标题】:Is there a way to do a kind of TryStart for a task in c# TPL (without locking)?有没有办法为 c# TPL 中的任务执行一种 TryStart(不锁定)?
【发布时间】:2015-10-23 11:02:26
【问题描述】:

这样如果任务还没有被另一个线程启动,任务就会启动。

例如

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(),使其保持线程安全?

【问题讨论】:

  • 你可以使用Lazy&lt;T&gt;
  • 您使用的是什么框架版本? .NET 4.5 中不需要此代码

标签: c# .net locking task-parallel-library task


【解决方案1】:

如果我理解正确,您是在尝试懒惰地创建一些资源。你可以通过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 并返回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;
}

【讨论】:

  • @ClayVerValen 我正在检查null。也许正在查看我的答案的早期版本?
  • @PanagiotisKanavos 我最初与tcs.Task 进行了比较,因为我错误地记得返回值是新值而不是原始值。我想这引起了一些混乱。
  • 真正的困惑是 - OP 使用 4.5 还是 4.0?语法看起来像 4.0,Lazy 仅在 4.5+ 中可用。
  • @PanagiotisKanavos 我从来没有让用户提出问题然后离开。他们不想得到答案吗?
【解决方案2】:

我会像@i3arnon 所说的那样使用AsyncLazy&lt;T&gt;,或者公开Task&lt;Resource&gt; 而不是同步阻塞它:

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;
}

【讨论】:

    猜你喜欢
    • 2011-03-07
    • 2021-04-22
    • 2019-10-04
    • 1970-01-01
    • 2021-09-16
    • 2015-11-02
    • 1970-01-01
    • 1970-01-01
    • 2014-02-27
    相关资源
    最近更新 更多