2011-12-23 96 views
12

如果用戶執行操作(例如刪除項目),則會立即從UI中刪除它們,然後使用TPL將它們從後臺線程的數據庫中刪除。問題是,如果用戶在後臺線程結束之前退出應用程序,則該項目永遠不會被刪除。如何等待我的異步操作在應用程序退出時完成?

在關閉應用程序之前是否有等待異步操作完成的標準方式?

我的異步調用是這樣的:

if (MyObjectList.Contains(obj)) MyObjectList.Remove(obj); 
Task.Factory.StartNew(() => DAL<MyEntities>.DeleteObject(obj)); 

更新

下面是最終代碼我去了。我很高興看到它能夠正常工作,但讓我知道我是否可以改進它。我還有很多東西需要學習:)

public partial class App : Application 
{ 
    private List<Task> _backgroundTasks = new List<Task>(); 

    public App() 
    { 
     EventSystem.Subscribe<TaskStartedMessage>((e) => 
     { 
      _backgroundTasks.Add(e.Task); 
     }); 

     EventSystem.Subscribe<TaskEndedMessage>((e) => 
     { 
      if (_backgroundTasks.Contains(e.Task)) 
       _backgroundTasks.Remove(e.Task); 
     }); 
    } 

    protected override void OnExit(ExitEventArgs e) 
    { 
     Task.WaitAll(_backgroundTasks.Where(p => !p.IsCompleted).ToArray(), 30000); 

     base.OnExit(e); 
    } 
} 

和啓動重要的背景任務時,我使用這個語法:

var task = Task.Factory.StartNew(() => DAL<MyEntities>.DeleteObject(obj)); 
EventSystem.Publish<TaskStartedMessage>(new TaskStartedMessage(task)); 
await task; 
EventSystem.Publish<TaskEndedMessage>(new TaskEndedMessage(task)); 

我使用AsyncCTP爲await/async和微軟Prism的EventAggregator爲事件系統。

+1

最終代碼看起來不錯,但是我刪除了foreach並將其替換爲:Task.WaitAll(_backgroundTasks.ToArray());我想不出你的實現可能會造成什麼麻煩,但我猜測如果他們實現了這個靜態方法,他們有一個問題。 –

+0

@Baboon謝謝,我不知道有一個'Task.WaitAll()'方法,我可以在任務列表上使用 – Rachel

回答

10

沒有標準的方法,但是由於您在這裏創建了一個特定的任務,應該很容易將它放入列表中並構建一些退出邏輯以等待該列表中的所有任務。

好吧,樣品。未經測試和不完整:

// untested 
static class CriticalTasks 
{ 
    static HashSet<Task> tasks = new HashSet<Task>(); 
    static object locker = new object(); 

    // when starting a Task 
    public static void Add(Task t) 
    { 
     lock(locker) 
      tasks.Add(t); 
    } 

    // When a Tasks completes 
    public static void Remove(Task t) 
    { 
     lock(locker) 
      tasks.Remove(t); 
    } 

    // Having to call Remove() is not so convenient, this is a blunt solution. 
    // call it regularly 
    public static void Cleanup() 
    { 
     lock(locker) 
      tasks.RemoveWhere(t => t.Status != TaskStatus.Running); 
    } 

    // from Application.Exit() or similar. 
    public static void WaitOnExit() 
    { 
     // filter, I'm not sure if Wait() on a canceled|completed Task would be OK 
     var waitfor = tasks.Where(t => t.Status == TaskStatus.Running).ToArray(); 
     Task.WaitAll(waitfor, 5000); 
    } 
} 


的缺點是,你必須給每個任務的代碼擴展到添加&刪除它。

忘記一個Remove()(例如,當一個異常發生時)將是一個(小)內存泄漏。這不是太關鍵,而是用using()塊代替負擔,您也可以定期運行使用HashSet.RemoveWhere()移除非運行任務的Cleanup()方法。

+0

你能提供一個例子嗎? – Rachel

+0

謝謝:)我喜歡看代碼示例,因爲它指向正確的方向,我經常可以從中學習新的東西。 – Rachel

+0

@Rachel,好,但我現在意識到你需要使它線程安全。我稍後會編輯。 –

0

您可以存儲對任務的引用,並在應用程序退出時等待它(例如,在退出事件時)。

您也可以創建一個普通線程,請確保您將IsBackground設置爲false(默認值)。在所有非後臺線程完成工作之前,進程不會退出,因此它將運行到最後,您將不得不注意不要使用任何GUI邏輯或確保不會從此線程中處理需要的對象。

+0

在後臺線程上運行它的要點是防止它在刪除對象時鎖定UI。這不會在主線程上運行刪除代碼並鎖定用戶界面嗎? – Rachel

+0

@Rachel:如果它是UI的背景,那麼作爲背景的背景無關。後臺線程是所有非後臺線程退出時應該終止的線程。非後臺線程將繼續它的工作。因此,它在非後臺線程上運行數據庫邏輯,它將獨立完成主界面的工作。 –

0
var x = Task.Factory.StartNew(() => DAL<MyEntities>.DeleteObject(obj)); 

在形式關閉事件:

while(!x.IsCompleted){hide form} 

或者

if(!x.IsCompleted) 
    //cancel exit 
+1

我真的建議,以避免這種紡紗,將核心提高到100% –

0

代替一個蹩腳代碼的例子。答案是你根本不能。如果應用程序域開始終止,則在所有此類任務被強制終止之前,您都有一個有限的窗口來執行清理任務。你最好的行動方式是進行某種工作控制,你有條件地排隊工作。這也可以防止異常終止。

否則,您只需在執行WaitAll並完全完成之前不要開始退出。

+1

你可以添加一些關於你的描述回答? – benka

+0

是的。我會添加評論。道歉。簡短的回答是,如果您希望完成所有任務,您無法退出程序。因此,將Tasks.WaitAll放置在OnExit處理程序中是一個糟糕的選擇,因爲一旦應用程序域決定將其關閉,您就有一小段時間來執行清理,並且一旦該時間段結束,應用程序將強制終止。所以基本上我的代碼只是在程序退出之前執行WaitAll權限。如果您希望絕對不會失敗,那麼您需要具備某種具備恢復功能的作業控制系統。 – user3524983