2016-04-19 14 views
1

SchedulerというクラスがDictionary<UserId, Task>を含むとします。タスクは、そのユーザーの予定を内部的にスケジュールしてループして更新します<UserId, Schedule>つまりリアルタイムで情報を更新したいと思っています。タスク内のループが少なくとも1回実行されていることを確認する方法

私はそこにそのユーザーのタスクがあるとするかどうかをチェックSchedulerクラスGetScheduleForUser上のメソッドを持つようにしたい、それが終了した後、(そのユーザーの怠惰Schedulesを取得ティルない、それはタスク待ちを作成します場合それをロードする)。

私の質問は、タスクの最初の反復の後に私は利用可能なスケジュールを持っているでしょう、私はただスケジュールを取得することができます...問題はありませんが、最初の反復では、タスクが少なくとも終了するまで待つ必要がありますスケジュールを取得する前に1回。

最初のループで終了したときに特定のフラグが設定されるまで、タスクを開始してwhileループを作成することはできますが、より良い方法があるように思えます繰り返し。その後、スケジュールは常に利用可能になり、機能は必要ありません。

誰でもこれを達成するためのきれいな方法がありますか?

+0

あなたは私がそれをやって考えていた 'TaskCompletionSource、SemaphoreSlim、ManualResetEventSlim、Mutex'などのような同期プリミティブの1 .. – Eser

+0

を使用することができます。問題は、私がすべてのデータを持っていることを初めて確認する必要があるにしても毎回手動イベントをリセットすることです。 – Eitan

+0

'Lazy'と' ConcurrentDictionary'を使ってみましょう:https:// blogs .endjin.com/2015/10/using-lazy-and-concurrentdictionary-thread-safe-once-lazy-loaded-collection/ –

答えて

0

私が考えることができる最良の解決策は、彼のコメントで述べたTaskCompletionSourceを使用することです。ここでは、それが何をしているのかを簡単に追跡できるように、たくさんのコンソール出力がある大まかなコードサンプルがあります。私もIDisposableをスケジューラのカルスに追加し、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

関連する問題