2016-02-02 62 views
1

正是我正在做的正确/最好的方式来完成这一点?正确的方式做任务同步?

我有一个带定时器的窗口。每次计时器滴答时,我都会调用下面显示的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; 
      } 
     } 
    } 
} 

由于程序的架构,我宁愿把我所有的线程同步的这种方法,而不是启用和禁用计时器内。

回答

3

通过略微不同的思考问题,它变得更容易。而不是每隔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)) 
+1

当任务运行5秒钟,第一次运行将在第二次,第二次将在第二次15.我认为OP希望第一次运行在0和第二次在10(或如果该任务在0和20处需要15秒)。你可以使用Task.Whenall而不是2个独立的等待来实现这个结果。 – wertzui

+0

@wertzui如果OP更喜欢你对时间的解释,我在代码中添加了一些注释来演示替代方案。 – spender

1

如果你担心太多的锁定,你可以做到以下几点。如果一个任务完成,而另一个任务完成时,您可能会错过一次运行,而另一个只是在支票(标记)处,但您摆脱了一些锁定,只需在设置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; 
     } 
    } 
}