2016-04-19 80 views
1

比方說,我有一個叫Scheduler類,它包含的<UserId, Task>一個Dictionary,任務不斷循環,並與調度與信息更新內部的詞典爲用戶<UserId, Schedule>一個數據庫,即我想保持信息實時更新。如何確保在任務中循環至少運行一次

我想對SchedulerGetScheduleForUser進行檢查,看是否有該用戶的任務和方法,如果沒有它會直到它完成,然後爲用戶檢索Schedules(懶惰創建任務等待加載它)。

我的問題是,在任務的第一次迭代後,我將有一個可用的時間表,我可以檢索時間表...沒問題,但對於第一次迭代,我需要等到任務完成至少一次檢索時間表。

我可以開始任務並創建一個while循環,直到第一次循環完成時設置某個標誌爲止,但在我看來,似乎有更好的方法,它只對第一個循環有用迭代。之後,時間表將始終可用,我不需要該功能。

有沒有人有一個乾淨的方式來實現這一目標?

+0

您可以使用像'TaskCompletionSource,SemaphoreSlim,ManualResetEventSlim,Mutex'等同步原語的一個.. – Eser

+0

我想這樣做的。問題是我每次都會重置手動事件,即使我第一次只需要它來確保我擁有所有數據 – Eitan

+0

請查看使用'Lazy'和'ConcurrentDictionary':https:// blogs .endjin.com/2015/10/using-lazy-and-concurrentdictionary-to-ensure -a-thread-safe-run-once-lazy-loaded-collection/ –

回答

0

我能想到的最佳解決方案將使用TaskCompletionSource,Eser在他的評論中提到。這裏有一個粗略的代碼示例,其中包含許多控制檯輸出,以便更輕鬆地跟蹤它正在執行的操作。我還將IDisposable添加到計劃程序calss和CancellationTokenSource的字典中,以便在完成調度程序時,它可以停止所有的任務。

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 

public class Program 
{ 
    // Helper property to simplify console output 
    public static string TimeString { get { return DateTime.Now.ToString("mm:ss.fff"); } } 

    public static void Main(string[] args) 
    { 
     using (var scheduler = new Scheduler()) 
     { 
      var userID = "1"; 

      Console.WriteLine(TimeString + " Main: Getting schedule for first time..."); 
      var sched1 = scheduler.GetScheduleForUser(userID); 
      Console.WriteLine(TimeString + " Main: Got schedule: " + sched1); 

      Console.WriteLine(TimeString + " Main: Waiting 2 seconds..."); 
      System.Threading.Thread.Sleep(2000); 

      Console.WriteLine(TimeString + " Main: Getting schedule for second time..."); 
      var sched2 = scheduler.GetScheduleForUser(userID); 
      Console.WriteLine(TimeString + " Main: Got schedule: " + sched2); 
     } 

     Console.WriteLine(); 
     Console.WriteLine("Press any key to end . . ."); 
     Console.ReadKey(); 
    } 
} 

public class Scheduler : IDisposable 
{ 
    // Helper property to simplify console output 
    public static string TimeString { get { return DateTime.Now.ToString("mm:ss.fff"); } } 

    private Dictionary<string, Task> TasksDictionary { get; set; } 
    private Dictionary<string, TaskCompletionSource<bool>> TaskCompletionSourcesDictionary { get; set; } 
    private Dictionary<string, CancellationTokenSource> CancellationTokenSourcesDictionary { get; set; } 
    private Dictionary<string, string> SchedulesDictionary { get; set; } 

    public Scheduler() 
    { 
     TasksDictionary = new Dictionary<string, Task>(); 
     TaskCompletionSourcesDictionary = new Dictionary<string, TaskCompletionSource<bool>>(); 
     CancellationTokenSourcesDictionary = new Dictionary<string, CancellationTokenSource>(); 
     SchedulesDictionary = new Dictionary<string, string>(); 
    } 

    public void Dispose() 
    { 
     if (TasksDictionary != null) 
     { 
      if (CancellationTokenSourcesDictionary != null) 
      { 
       foreach (var tokenSource in CancellationTokenSourcesDictionary.Values) 
        tokenSource.Cancel(); 

       Task.WaitAll(TasksDictionary.Values.ToArray(), 10000); 

       CancellationTokenSourcesDictionary = null; 
      } 

      TasksDictionary = null; 
     } 

     CancellationTokenSourcesDictionary = null; 
     SchedulesDictionary = null; 
    } 

    public string GetScheduleForUser(string userID) 
    { 
     // There's already a schedule, so get it 
     if (SchedulesDictionary.ContainsKey(userID)) 
     { 
      Console.WriteLine(TimeString + "  GetSchedule: Already had schedule for user " + userID); 
      return SchedulesDictionary[userID]; 
     } 

     // If there's no task yet, start one 
     if (!TasksDictionary.ContainsKey(userID)) 
     { 
      Console.WriteLine(TimeString + "  GetSchedule: Starting task for user " + userID); 
      var tokenSource = new CancellationTokenSource(); 
      var token = tokenSource.Token; 
      TaskCompletionSourcesDictionary.Add(userID, new TaskCompletionSource<bool>()); 
      var task = (new TaskFactory()).StartNew(() => GenerateSchedule(userID, token, TaskCompletionSourcesDictionary[userID]), token); 
      TasksDictionary.Add(userID, task); 
      CancellationTokenSourcesDictionary.Add(userID, tokenSource); 
      Console.WriteLine(TimeString + "  GetSchedule: Started task for user " + userID); 
     } 

     // If there's a task running, wait for it 
     Console.WriteLine(TimeString + "  GetSchedule: Waiting for first run to complete for user " + userID); 
     var temp = TaskCompletionSourcesDictionary[userID].Task.Result; 
     Console.WriteLine(TimeString + "  GetSchedule: First run complete for user " + userID); 

     return SchedulesDictionary.ContainsKey(userID) ? SchedulesDictionary[userID] : "null"; 
    } 

    private void GenerateSchedule(string userID, CancellationToken token, TaskCompletionSource<bool> tcs) 
    { 
     Console.WriteLine(TimeString + "   Task: Starting task for userID " + userID); 

     bool firstRun = true; 
     while (!token.IsCancellationRequested) 
     { 
      // Simulate work while building schedule 
      if (token.WaitHandle.WaitOne(1000)) 
       break; 

      // Update schedule 
      SchedulesDictionary[userID] = "Schedule set at " + DateTime.Now.ToShortTimeString(); 
      Console.WriteLine(TimeString + "   Task: Updated schedule for userID " + userID); 

      // If this was the first run, set the result for the TaskCompletionSource 
      if (firstRun) 
      { 
       tcs.SetResult(true); 
       firstRun = false; 
      } 
     } 

     Console.WriteLine(TimeString + "   Task: Ended task for userID " + userID); 
    } 
} 

這裏有一個小提琴,顯示它在行動:.NET Fiddle