2013-07-13 20 views
17

Tôi đang cố gắng hiểu cách cập nhật giao diện người dùng từ một sự kiện trong khi sử dụng mẫu không đồng bộ/chờ. Dưới đây là mã thử nghiệm tôi đang sử dụng trên một ứng dụng WinForm. Tôi thậm chí không chắc chắn đây là cách đúng đắn để đi về nó. Điều gì là cần thiết để cho phép phương thức pwe_StatusUpdate cập nhật giao diện người dùng? Lỗi hoạt động chéo được ném ở đó.Cập nhật giao diện người dùng từ các sự kiện bằng cách sử dụng asyc await

Cảm ơn bạn đã đọc.

// calling code 
    ProcessWithEvents pwe = new ProcessWithEvents(); 
    pwe.StatusUpdate += pwe_StatusUpdate; 
    await pwe.Run(); 



void pwe_StatusUpdate(string updateMsg) 
    { 
     // Error Here: Cross-thread operation not valid: Control '_listBox_Output' accessed from a thread other than the thread it was created on. 
     _listBox_Output.Items.Add(updateMsg); 
    } 

-

// Class with long running process and event  
public delegate void StatusUpdateHandler(string updateMsg); 

public class ProcessWithEvents 
    { 
    public event StatusUpdateHandler StatusUpdate; 

    public async Task Run() 
    { 
     await Task.Run(() => 
     { 
      for (int i = 0; i < 10; i++) 
       { 

        RaiseUpdateEvent(String.Format("Update {0}", i)); 

        Thread.Sleep(500); 
       } 
      }); 

     } 

     private void RaiseUpdateEvent(string msg) 
     { 
     if (StatusUpdate != null) 
      StatusUpdate(msg); 
     } 
    } 

-

Trả lời

20

The async pattern has support for progress updates.

Tóm lại, phương thức async của bạn có thể mất IProgress<T> và mã gọi điện của bạn chuyển sang triển khai giao diện đó (thường là Progress<T>).

public class ProcessWithUpdates 
{ 
    public async Task Run(IProgress<string> progress) 
    { 
    await Task.Run(() => 
    { 
     for (int i = 0; i < 10; i++) 
     { 
     if (progress != null) 
      progress.Report(String.Format("Update {0}", i)); 
     Thread.Sleep(500); 
     } 
    }); 
    } 
} 

// calling code 
ProcessWithUpdates pwp = new ProcessWithUpdates(); 
await pwp.Run(new Progress<string>(pwp_StatusUpdate)); 
+0

'ProcessWithProgress' trong phần' // calling code' có phải là 'ProcessWithUpdates' thay thế không? –

+0

@Brock: Yup. Đã chỉnh sửa. –

2

Bạn nên sử dụng phương pháp InvokeControl. Nó thực hiện một số mã trong thread của Control. Ngoài ra, bạn có thể kiểm tra tài sản InvokeRequired để kiểm tra xem bạn có cần gọi đến phương thức Invoke hay không (nó sẽ kiểm tra xem người gọi có đang ở một luồng khác với trình điều khiển được tạo trên) hay không.

đơn giản ví dụ:

void SomeAsyncMethod() 
{ 
    // Do some work    

    if (this.InvokeRequired) 
    { 
     this.Invoke((MethodInvoker)(() => 
      { 
       DoUpdateUI(); 

      } 
     )); 
    } 
    else 
    { 
     DoUpdateUI(); 
    } 
} 

void DoUpdateUI() 
{ 
    // Your UI update code here 
} 

Trong một số trường hợp, bạn nên kiểm tra IsHandleCreated tài sản của Control trước khi gọi Invoke phương pháp. Nếu IsHandleCreated lợi nhuận sai thì bạn cần phải chờ đợi trong khi xử lý kiểm soát sẽ tạo

+0

này hoạt động tuyệt vời cũng như suggession tiến bộ. Tôi đang sử dụng cả hai phương pháp tiếp cận cảm ơn rất nhiều. – ChiliYago

+0

Bạn không nên tự thực hiện điều này khi sử dụng mẫu không đồng bộ. 'Progress ' đã được thực hiện trên SynchronizationContext mà nó đã được tạo trên đó. – cremor

+0

Giải pháp đơn giản. Hoạt động tuyệt vời. –

2

// Chỉ cần khai báo một delegate như vậy

delegate void Add(string msg); 

// Sau đó khai báo phương pháp đại biểu như vậy:

var add = new Add((msg) => { 
    _listBox_Output.Items.Add(msg); 
}); 

// Bây giờ, chỉ cần gọi cho người được ủy quyền:

void pwe_StatusUpdate(string updateMsg) 
    { 

     _listBox_Output.Invoke(add,updateMsg); 
    } 
0

Dưới đây là một ví dụ

async void DoExport() 
{ 
    var rMsg = ""; 
    var t = await Task<bool>.Factory.StartNew(() => ExportAsMonthReport(LastMonth.Name, LastYear.Name, out rMsg)); 

    if (t) 
    { 
      BeginInvoke((Action)(() => 
      { 
       spinnerMain.Visible = false; 
       menuItemMonth.Enabled = true; 

       MetroMessageBox.Show(this, rMsg, "Export", MessageBoxButtons.OK, MessageBoxIcon.Information, 200); 
      })); 
    } 
    else 
    { 
      BeginInvoke((Action)(() => 
      { 
       spinnerMain.Visible = false; 
       menuItemMonth.Enabled = true; 

       MetroMessageBox.Show(this, rMsg, "Export", MessageBoxButtons.OK, MessageBoxIcon.Error, 200); 
      })); 
    } 
}