执行任务同步的正确方法

本文关键字:方法 同步 执行任务 | 更新日期: 2023-09-27 18:33:04

我在下面所做的是实现此目的的正确/最佳方法吗?

我有一个带计时器的窗口。 每次计时器滴答作响时,我都会调用如下所示的RunTask方法。 在RunTask,我打电话给DoTheThingDoTheThing可能需要一段时间才能运行,并且可能会失败(这是数据库更新)。 我想确保在任何时候,我只有一个DoTheThing未完成。 我还想确保我没有一堆RunTask实例全部排队并等待正在运行DoTheThingRunTask实例释放锁。

public void RunTask()
{
    bool canRunTask = true;
    // Check if another instance of this method is currently executing.  If so, do not execute the rest of this method
    lock (this.runTaskLock)
    {
        if (this.isTaskRunning)
        {
            canRunTask = false;
        }
        else
        {
            this.isTaskRunning = true;
        }
    }
    // Call DoTheThing if another instance is not currently outstanding
    if (canRunTask)
    {
        try
        {
            Task task = new Task(() => DoTheThing());
            task.Start();
        }
        catch (Exception ex)
        {
            // Handle the exception
        }
        finally
        {
            lock (this.runTaskLock)
            {
                this.isTaskRunning = false;
            }
        }
    }
}

由于程序的体系结构,我宁愿将所有线程同步放在此方法中,而不是启用和禁用计时器。

执行任务同步的正确方法

通过稍微不同地思考问题,它变得容易得多。与其每 x 秒触发一次计时器,为什么不在调用之间等待 x 秒呢?

现在,您只需运行一个异步循环来执行计划的工作,并为自己节省一堆痛苦的同步工作。

async Task RunActionPeriodicallyAsync(Action action, 
                           TimeSpan ts, 
                           CancellationToken token = default(CancellationToken))
{
    while(!token.IsCancellationRequested)
    {
        action();
        await Task.Delay(ts, token);
        //or alternatively (see comment below)
        //var delayTask = Task.Delay(ts, token);
        //action();
        //await delayTask;
    }
}

现在,只需调用RunActionPeriodicallyAsync一次,对其操作的调用将永远不会重叠。

RunActionPeriodicallyAsync(() => DoSomething(), TimeSpan.FromSeconds(10))

您可以重载它以采取异步"操作"......实际上是一个Func<Task>...

async Task RunActionPeriodicallyAsync(Func<CancellationToken, Task> actionAsync, 
                           TimeSpan ts, 
                           CancellationToken token = default(CancellationToken))
{
    while(!token.IsCancellationRequested)
    {
        await actionAsync(token);
        await Task.Delay(ts, token);
        //or alternatively (see comment below)
        //await Task.WhenAll(actionAsync(token), Task.Delay(ts, token))
    }
}

并使用它:

RunActionPeriodicallyAsync(async cancTok => await DoSomethingAsync(cancTok), 
                           TimeSpan.FromSeconds(10))

如果您担心锁定过多,可以执行以下操作。如果一个任务完成而另一个任务只是在检查(标记),您可能会错过运行,但是您摆脱了一些锁定,并且只需要在设置isTaskRunnung = true时锁定。此外,您需要将方法标记为异步,以便等待任务。

public async Task RunTask()
{
    bool canRunTask = true;
    // Check if another instance of this method is currently executing.  If so, do not execute the rest of this method
    if (this.isTaskRunning)
    {                                       // <-- ___MARK___
        canRunTask = false;
    }
    else
    {
        lock (this.runTaskLock)
        {
            if (this.isTaskRunning)
            {
                canRunTask = false;
            }
            else
            {
                    this.isTaskRunning = true;
            }
        }
    }
    // Call DoTheThing if another instance is not currently outstanding
    if (canRunTask)
    {
        try
        {
            await Task.Run(() => DoTheThing());
        }
        catch (Exception ex)
        {
            // Handle the exception
        }
        finally
        {
            this.isTaskRunning = false;
        }
    }
}