如果已经有一个任务正在运行,多个线程等待该任务的完成。

3

我有一个应用,其中有不同的后台定时器正在与API进行同步。

然而,在实际同步之前,它们都会先ping API,看它们是否可以继续。为了避免多次调用,如果已经有相同的Task在运行,我让它们等待该Task。当Task返回结果后,等待的进程应该得到结果。同时,如果调用了CanExecute方法且Task未在运行,则应重新运行Task

protected async Task<bool> CanExecute([CallerMemberName] string memberName = "")
{
    lock (_lockobj)
    {
        if (_runningTask == null)
        {
           _runningTask = CanExecuteTask(memberName);
        }
    }

    var result = await _runningTask;
    _runningTask = null;
    return result;
}

private async Task<bool> CanExecuteTask(string callingMemberName)
{
    var result = // do http call and some other method calls
    return result;
}

我不确定问题出在哪里,但我认为会发生死锁并且同步没有继续进行。如何正确解决这个问题?


你真的需要延迟调用 CanExecute 吗?如果不需要,你可以在构造函数中调用 _runningTask = CanExecuteTask 并在方法中使用 await。该任务仅启动一次,但每个线程都将等待直到任务完成。 - Panagiotis Kanavos
你能否尝试在CanExecuteTask中使用SemaphoreSlim类代替lock? - Ghassen
@PanagiotisKanavos 可能是可能的,但如果一个线程恰好等待它时,Task 已经完成,它会重新运行吗? - Expressingx
1
await 等待一个已经处于活动状态的任务,它不会导致任务开始。任务不是线程或函数。它们代表了一种“承诺”,即某些事情可能会在未来完成并产生一个值。在构造函数中启动任务并将其存储在字段中是一个常见的习惯用法。 - Panagiotis Kanavos
@PanagiotisKanavos 没错。我需要的是如果任务已经完成或根本没有开始,就再次调用该任务。在这种情况下,我不知道懒惰初始化如何帮助我。 - Expressingx
1
这不是问题所问的。如果您使用容量为1的Bounded channelFullMode = DropWrite或DropNewest,则有一个开箱即用的解决方案。这将确保如果worker已经忙碌,则任何尝试将任何内容发布到频道的worker都将被丢弃。 - Panagiotis Kanavos
1个回答

1

这段代码存在安全隐患 - 在锁之外既读取又写入了 _runningTask。因此,例如,一个线程可以执行以下操作:

_runningTask = null;

当另一个线程通过锁定语句,而任务不为null并且即将执行await _runningTask时,您可以避免出现NullReferenceException,因为会导致await null

如果您按照以下方式操作,可以保持相同的原则:

private Task<bool> _runningTask = Task.FromResult(false);
protected async Task<bool> CanExecute([CallerMemberName] string memberName = "") {
    Task<bool> task;
    lock (_lockobj) {
        if (_runningTask.IsCompleted) {
            _runningTask = CanExecuteTask(memberName);
        }
        task = _runningTask;
    }

    var result = await task;        
    return result;
}

我们不再将任务设置为null,然后检查它是否已完成(这意味着成功、失败或取消)。相反,我们检查任务是否已完成。我们也从已经完成的任务开始,而不是从null开始。

此外,我们在锁内部将_runningTask分配给本地变量,然后等待它,以避免在锁之外读取_runningTask字段。

从多个线程等待同一个任务是安全的,所以我们应该没问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接