2013-06-04 39 views
5

Tôi có ứng dụng WPF và tôi đang sử dụng thành phần BackgroundWorker để truy xuất một số dữ liệu từ giao diện người dùng và hiển thị dữ liệu trong giao diện người dùng.Theo dõi nhiều BackgroundWorkers

BackgroundWorkerWorkerReportsProgress = true để giao diện người dùng có thể được cập nhật định kỳ. BackgroundWorker cũng có WorkerSupportsCancellation = true để người dùng có thể hủy nó. Tất cả những công trình đó đều tuyệt vời!

Tôi gặp sự cố khi cố triển khai hành vi thứ ba và phức tạp hơn. Về cơ bản, người dùng cần có sự linh hoạt để bắt đầu một nhiệm vụ mới BackgroundWorker bất kỳ lúc nào kể cả khi một người hiện đang thực thi. Nếu tác vụ hiện đang thực thi và tác vụ mới được bắt đầu, nhiệm vụ cũ cần được đánh dấu là Aborted. Aborted tác vụ khác với Cancelled trong đó Aborted không được phép thực hiện thêm bất kỳ cập nhật giao diện người dùng nào. Nó phải được "hủy bỏ âm thầm".

Tôi đã bọc BackgroundWorker bên trong lớp AsyncTask và thêm IsAborted bit. Kiểm tra đối với bit IsAborted bên trong ProgressChangedRunWorkerCompleted ngăn các cập nhật giao diện người dùng khác. Tuyệt quá!

Tuy nhiên, cách tiếp cận này bị hỏng vì khi công việc mới được khởi động, CurrentTask được thay thế bằng phiên bản mới AsyncTask. Kết quả là sẽ khó theo dõi số CurrentTask.

Như đã lưu ý, trong TODO:, gần như tôi muốn đợi cho đến khi số CurrentTask hoàn tất sau khi hủy trước khi bắt đầu một tác vụ mới. Nhưng tôi biết điều này sẽ cung cấp trải nghiệm người dùng tồi vì chuỗi giao diện người dùng sẽ bị chặn cho đến khi tác vụ cũ hoàn tất.

Có cách nào tốt hơn để theo dõi nhiều số AsyncTasks để đảm bảo rằng những cái mới có thể được kích hoạt theo yêu cầu và những cái cũ bị hủy chính xác mà không có cập nhật giao diện người dùng nào khác? Có vẻ như không phải là một cách tốt để theo dõi CurrentTask ... Liệu TPL đó có cung cấp cách tốt hơn để xử lý những gì tôi đang theo sau không?

Dưới đây là các đoạn đáng chú ý Tôi có bên trong lớp Window của tôi:

private AsyncTask CurrentTask { get; set; } 

private class AsyncTask 
{ 
    private static int Ids { get; set; } 

    public AsyncTask() 
    { 
     Ids = Ids + 1; 

     this.Id = Ids; 

     this.BackgroundWorker = new BackgroundWorker(); 
     this.BackgroundWorker.WorkerReportsProgress = true; 
     this.BackgroundWorker.WorkerSupportsCancellation = true; 
    } 

    public int Id { get; private set; } 
    public BackgroundWorker BackgroundWorker { get; private set; } 
    public bool IsAborted { get; set; } 
} 

void StartNewTask() 
{ 
    if (this.CurrentTask != null && this.CurrentTask.BackgroundWorker.IsBusy) 
    { 
     AbortTask(); 

     //TODO: should we wait for CurrentTask to finish up? this will block the UI? 
    } 

    var asyncTask = new AsyncTask(); 

    asyncTask.BackgroundWorker.DoWork += backgroundWorker_DoWork; 
    asyncTask.BackgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged; 
    asyncTask.BackgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted; 

    AppendText("Starting New Task: " + asyncTask.Id); 

    this.CurrentTask = asyncTask; 

    asyncTask.BackgroundWorker.RunWorkerAsync(); 
} 

void AbortTask() 
{ 
    if (this.CurrentTask != null && this.CurrentTask.BackgroundWorker.IsBusy) 
    { 
     AppendText("Aborting Task " + this.CurrentTask.Id + "..."); 
     this.CurrentTask.IsAborted = true; 
     this.CurrentTask.BackgroundWorker.CancelAsync(); 
    } 
} 

void CancelTask() 
{ 
    if (this.CurrentTask != null && this.CurrentTask.BackgroundWorker.IsBusy) 
    { 
     AppendText("Cancelling Task " + this.CurrentTask.Id + "..."); 
     this.CurrentTask.BackgroundWorker.CancelAsync(); 
    } 
} 

void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    var backgroundWorker = (BackgroundWorker)sender; 

    for (var i = 0; i < 10; i++) 
    { 
     //check before making call... 
     if (backgroundWorker.CancellationPending) 
     { 
      e.Cancel = true; 
      return; 
     } 

     //simulate a call to remote service... 
     Thread.Sleep(TimeSpan.FromSeconds(10.0)); 

     //check before reporting any progress... 
     if (backgroundWorker.CancellationPending) 
     { 
      e.Cancel = true; 
      return; 
     } 

     backgroundWorker.ReportProgress(0); 
    } 
} 

void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
{ 
    if (this.CurrentTask.IsAborted) 
     return; 

    AppendText("[" + DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss") + "] " + "Progress on Task: " + this.CurrentTask.Id + "..."); 
} 

void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    if (this.CurrentTask.IsAborted) 
     return; 

    if (e.Cancelled) 
    { 
     AppendText("Cancelled Task: " + this.CurrentTask.Id); 
    } 
    else if (e.Error != null) 
    { 
     AppendText("Error Task: " + this.CurrentTask.Id); 
    } 
    else 
    { 
     AppendText("Completed Task: " + this.CurrentTask.Id); 
    } 

    //cleanup... 
    this.CurrentTask.BackgroundWorker.DoWork -= backgroundWorker_DoWork; 
    this.CurrentTask.BackgroundWorker.ProgressChanged -= backgroundWorker_ProgressChanged; 
    this.CurrentTask.BackgroundWorker.RunWorkerCompleted -= backgroundWorker_RunWorkerCompleted; 
    this.CurrentTask= null; 
} 
+0

Có vẻ như những gì bạn muốn thực hiện là bạn muốn chạy một quy trình dài nhưng cho phép người dùng giết quá trình chạy này giữa chừng và bắt đầu một quy trình mới. Điều này có đúng không? – TheKingDave

+0

Có. Tuy nhiên, một nhiệm vụ có thể bị giết qua Hủy hoặc thông qua Hủy bỏ. Hủy được gọi bởi người dùng. Ở đây, thoát khỏi chuỗi nhưng hoàn toàn miễn phí để hoàn tất cập nhật giao diện người dùng. Hủy bỏ được gọi khi tác vụ thứ hai được tạo và bắt đầu nếu một tác vụ khác hiện đang thực thi. Nhiệm vụ ban đầu phải bị hủy bỏ. Trong trường hợp này, nhiệm vụ mới có quyền ưu tiên và nhiệm vụ bị hủy bỏ cũ không thể cập nhật giao diện người dùng và phải "thoát âm thầm". –

+0

Vì vậy, nếu bạn "Hủy bỏ" nhiệm vụ nên hệ thống kết thúc quá trình chạy dài hay không?Nếu tôi là một người dùng và tôi đã chọn hủy bỏ, tôi có thể mong đợi hệ thống dừng mọi thứ nếu tôi chọn hủy bỏ, tôi mong đợi nó dừng lại một cách duyên dáng. – TheKingDave

Trả lời

9

Từ những gì tôi hiểu, bạn không muốn thực sự hủy bỏ các chủ đề, bạn chỉ muốn nó tiếp tục làm việc âm thầm (tức là không cập nhật giao diện người dùng)? Một cách là giữ một danh sách các BackgroundWorkers và loại bỏ các trình xử lý sự kiện của chúng nếu chúng bị "hủy bỏ".

List<BackgroundWorker> allBGWorkers = new List<BackgroundWorker>(); 

//user creates a new bg worker. 
BackgroundWorker newBGWorker = new BackgroundWorker(); 
//.... fill out properties 


//before adding the new bg worker to the list, iterate through the list 
//and ensure that the event handlers are removed from the existing ones  
foreach(var bg in allBGWorkers) 
{  
    bg.ProgressChanged -= backgroundWorker_ProgressChanged; 
    bg.RunWorkerCompleted -= backgroundWorker_RunWorkerCompleted; 
} 

//add the latest bg worker you created 
allBGWorkers.Add(newBGWorker); 

Bằng cách đó bạn có thể theo dõi tất cả công nhân. Kể từ khi List duy trì thứ tự, bạn sẽ biết cái nào mới nhất là (cái cuối cùng trong danh sách) nhưng bạn có thể dễ dàng sử dụng một số Stack tại đây nếu bạn thích.

+0

Cảm ơn bạn đã trả lời nhanh. Đó là chính xác những gợi ý tôi đang tìm kiếm. Thay vì gói BackgroundWorker và kiểm tra đối với IsAborted, chỉ cần loại bỏ các trình xử lý sự kiện là dễ dàng hơn nhiều để quản lý một chuỗi "hủy bỏ". Ngoài ra, Ngăn xếp là cấu trúc dữ liệu hoàn hảo để luôn theo dõi hiện tại, tức là phần Trên cùng. Cảm ơn! –

+0

Bạn được chào đón :) – keyboardP