2015-07-09 115 views
1

我正在編寫一組異步任務,這些任務會消除下載和解析數據,但是我在下一步更新數據庫時遇到了一些空白。將異步任務與阻塞同步任務混合使用

問題是,出於性能的考慮,我使用TableLock來加載相當大的數據集,所以我想要做的是讓我的導入服務等待第一個任務返回,開始導入。如果在第一次導入運行時完成另一個任務,則該過程將加入隊列並等待任務1的導入服務已完成。

例如,

異步 - 任務1 - 任務2 - 任務3

同步 - ImportService

RunAsync任務

Task3 returns first > ImportService.Import(Task3) 
Task1 return, ImportService is still running. Wait() 
ImportService.Complete() event 
Task2 returns. Wait() 
ImportService.Import(Task1) 
ImportService.Complete() event 
ImportService.Import(Task2) 
ImportService.Complete() event 

希望這是有道理的!

+0

您可能應該考慮[TPL DataFlow](https://msdn.microsoft.com/library/hh228603.aspx「Dataflow(任務並行庫)」)。 –

+0

Paulo,那正是我正在尋找的!謝謝! –

回答

1

雖然這可能不是最優雅的解決方案,但我會嘗試啓動工作任務並讓他們將輸出放在ConcurrentQueue中。您可以檢查隊列中的計時器,直到完成所有任務。

var rand = new Random(); 
var importedData = new List<string>(); 
var results = new ConcurrentQueue<string>(); 
var tasks = new List<Task<string>> 
{ 
    new Task<string>(() => 
    { 
     Thread.Sleep(rand.Next(1000, 5000)); 
     Debug.WriteLine("Task 1 Completed"); 
     return "ABC"; 
    }), 
    new Task<string>(() => 
    { 
     Thread.Sleep(rand.Next(1000, 5000)); 
     Debug.WriteLine("Task 2 Completed"); 
     return "FOO"; 
    }), 
    new Task<string>(() => 
    { 
     Thread.Sleep(rand.Next(1000, 5000)); 
     Debug.WriteLine("Task 3 Completed"); 
     return "BAR"; 
    }) 
}; 

tasks.ForEach(t => 
{ 
    t.ContinueWith(r => results.Enqueue(r.Result)); 
    t.Start(); 
}); 

var allTasksCompleted = new AutoResetEvent(false); 
new Timer(state => 
{ 
    var timer = (Timer) state; 
    string item; 

    if (!results.TryDequeue(out item)) 
     return; 

    importedData.Add(item); 
    Debug.WriteLine("Imported " + item); 

    if (importedData.Count == tasks.Count) 
    { 
     timer.Dispose(); 
     Debug.WriteLine("Completed."); 
     allTasksCompleted.Set(); 
    } 
}).Change(1000, 100); 


allTasksCompleted.WaitOne(); 
+0

'Task.WhenAll'可以簡化你的代碼。 – EZI

2

你真的不能用等待在這裏,但你可以等待多個任務來完成:

var tasks = new List<Task)(); 
// start the tasks however 
tasks.Add(Task.Run(Task1Function); 
tasks.Add(Task.Run(Task2Function); 
tasks.Add(Task.Run(Task2Function); 

while (tasks.Count > 0) 
{ 
    var i = Task.WaitAny(tasks.ToArray()); // yes this is ugly but an array is required 
    var task = tasks[i]; 
    tasks.RemoveAt(i); 
    ImportService.Import(task); // do you need to pass the task or the task.Result 
} 

在我看來,不過應該有一個更好的選擇。你可以讓任務和進口跑在ImportService部分例如加鎖:

// This is the task code doing whatever 
.... 
// Task finishes and calls ImportService.Import 
lock(typeof(ImportService)) // actually the lock should probably be inside the Import method 
{ 
    ImportService.Import(....); 
} 

有幾件事情困擾着我你的要求(包括使用靜態ImportService,靜態類很少是個好主意),但沒有進一步的細節,我無法提供更好的建議。

+1

對於OP:我同意Eli你應該簡單地同步導入,每個任務在導入之前獲取鎖定作爲最後的操作。根據具體的實現,這個主題可能會有更好的變化,但上面的例子和合理提供的一樣好,除非你用更多的細節來改進問題,包括[一個好的,_minimal_,_complete_代碼示例]( https://stackoverflow.com/help/mcve),這清楚地說明了這個問題。 –

+0

謝謝伊利和彼得。 我非常喜歡鎖定線程的想法,這是我原先想到的,但並不認爲這是可能的任務,你每天都會學到新的東西。謝謝! :) –

+0

@Al。你應該考慮你是否需要在應用程序級別鎖定。我不知道你使用的是什麼數據庫,但數據庫最適合處理併發操作。如果您在應用程序級別進行鎖定,假設甚至需要鎖定,那麼您僅限於同時運行的單個應用程序。無需重寫鎖定機制,您無法擴展到多個服務器。也許你的問題應該是:我如何讓我的查詢在沒有TableLock的情況下運行,或者沒有應用程序級鎖。 –