2009-01-19 15 views
11

Tôi hiện đang cố gắng viết một thành phần mà một số phần của nó sẽ chạy trên chuỗi giao diện người dùng (giải thích sẽ là dài). Vì vậy, cách dễ nhất là chuyển một điều khiển đến nó và sử dụng InvokeRequired/Invoke trên đó. Nhưng tôi không nghĩ rằng đó là một thiết kế tốt để chuyển một tham chiếu điều khiển đến một thành phần "dữ liệu/nền", vì vậy tôi đang tìm cách chạy mã trên chuỗi giao diện người dùng mà không cần điều khiển có sẵn. Something như Application.Dispatcher.Invoke trong WPF ...Chạy mã trên chuỗi giao diện người dùng mà không có đối tượng điều khiển hiện tại

bất kỳ ý tưởng, thx Martin

+6

Vui lòng đánh dấu một câu trả lời như được chấp nhận nếu một giải quyết vấn đề của bạn. – SandRock

Trả lời

2

Bạn là đúng, nó không phải là tốt để vượt qua điều khiển để đề. Điều khiển Winforms là đơn luồng, chuyển chúng đến nhiều chủ đề có thể gây ra điều kiện chủng tộc hoặc phá vỡ giao diện người dùng của bạn. Thay vào đó, bạn nên làm cho các tính năng của thread của bạn có sẵn cho giao diện người dùng và để cho nó gọi thread khi giao diện người dùng là tốt và sẵn sàng. Nếu bạn muốn có chủ đề nền kích hoạt thay đổi giao diện người dùng, hãy hiển thị sự kiện nền và đăng ký sự kiện đó từ giao diện người dùng. Chủ đề có thể kích hoạt các sự kiện bất cứ khi nào nó muốn và giao diện người dùng có thể phản hồi chúng khi có thể.

Tạo giao tiếp hai chiều giữa các chủ đề không chặn chuỗi giao diện người dùng là rất nhiều công việc. Dưới đây là một ví dụ viết tắt đánh giá cao bằng một lớp BackgroundWorker:

public class MyBackgroundThread : BackgroundWorker 
{ 
    public event EventHandler<ClassToPassToUI> IWantTheUIToDoSomething; 

    public MyStatus TheUIWantsToKnowThis { get { whatever... } } 

    public void TheUIWantsMeToDoSomething() 
    { 
     // Do something... 
    } 

    protected override void OnDoWork(DoWorkEventArgs e) 
    { 
     // This is called when the thread is started 
     while (!CancellationPending) 
     { 
      // The UI will set IWantTheUIToDoSomething when it is ready to do things. 
      if ((IWantTheUIToDoSomething != null) && IHaveUIData()) 
       IWantTheUIToDoSomething(this, new ClassToPassToUI(uiData)); 
     } 
    } 
} 


public partial class MyUIClass : Form 
{ 
    MyBackgroundThread backgroundThread; 

    delegate void ChangeUICallback(object sender, ClassToPassToUI uiData); 

    ... 

    public MyUIClass 
    { 
     backgroundThread = new MyBackgroundThread(); 

     // Do this when you're ready for requests from background threads: 
     backgroundThread.IWantTheUIToDoSomething += new EventHandler<ClassToPassToUI>(SomeoneWantsToChangeTheUI); 

     // This will run MyBackgroundThread.OnDoWork in a background thread: 
     backgroundThread.RunWorkerAsync(); 
    } 


    private void UserClickedAButtonOrSomething(object sender, EventArgs e) 
    { 
     // Really this should be done in the background thread, 
     // it is here as an example of calling a background task from the UI. 
     if (backgroundThread.TheUIWantsToKnowThis == MyStatus.ThreadIsInAStateToHandleUserRequests) 
      backgroundThread.TheUIWantsMeToDoSomething(); 

     // The UI can change the UI as well, this will not need marshalling. 
     SomeoneWantsToChangeTheUI(this, new ClassToPassToUI(localData)); 
    } 

    void SomeoneWantsToChangeTheUI(object sender, ClassToPassToUI uiData) 
    { 
     if (InvokeRequired) 
     { 
      // A background thread wants to change the UI. 
      if (iAmInAStateWhereTheUICanBeChanged) 
      { 
       var callback = new ChangeUICallback(SomeoneWantsToChangeTheUI); 
       Invoke(callback, new object[] { sender, uiData }); 
      } 
     } 
     else 
     { 
      // This is on the UI thread, either because it was called from the UI or was marshalled. 
      ChangeTheUI(uiData) 
     } 
    } 
} 
+1

Tôi thích phương pháp này vì nó tách biệt mã giao diện người dùng khỏi công việc nền. Nó cũng cho phép nhiều "người nghe" phản hồi các cập nhật trạng thái công việc nền. –

1

Đặt thao tác giao diện người dùng trong một phương pháp trên biểu mẫu để được thao tác và thông qua một đại biểu vào mã chạy trên thread nền, à la APM. Bạn không phải sử dụng params object p, bạn có thể mạnh mẽ nhập nó cho phù hợp với mục đích của riêng bạn. Đây chỉ là một mẫu chung đơn giản.

delegate UiSafeCall(delegate d, params object p); 
void SomeUiSafeCall(delegate d, params object p) 
{ 
    if (InvokeRequired) 
    BeginInvoke(d,p);   
    else 
    { 
    //do stuff to UI 
    } 
} 

Cách tiếp cận này được xác định trên thực tế là một đại biểu đề cập đến một phương thức trên một trường hợp cụ thể; bằng cách thực hiện việc thực hiện một phương thức của biểu mẫu, bạn đưa biểu mẫu vào phạm vi là this. Sau đây là giống hệt về mặt ngữ nghĩa.

delegate UiSafeCall(delegate d, params object p); 
void SomeUiSafeCall(delegate d, params object p) 
{ 
    if (this.InvokeRequired) 
    this.BeginInvoke(d,p);   
    else 
    { 
    //do stuff to UI 
    } 
} 
1

Điều gì về việc vượt qua System.ComponentModel.ISynchronizeInvoke? Bằng cách đó bạn có thể tránh đi qua một điều khiển.

18

Có một cách trừu tượng hơn tốt hơn để làm điều này hoạt động trên cả hai WinForms và WPF:

System.Threading.SynchronizationContext.Current.Post(theMethod, state); 

này hoạt động vì WindowsForms cài đặt một đối tượng WindowsFormsSynchronizationContext như bối cảnh đồng bộ hiện hành. WPF làm một cái gì đó tương tự, cài đặt riêng của nó bối cảnh đồng bộ hóa chuyên ngành (DispatcherSynchronizationContext).

.Post tương ứng với control.BeginInvoke.Send tương ứng với control.Invoke.

+0

SynchronizationContext.Current is null khi tôi cố gọi nó ... D: –

+2

Truy cập SyncrhonizationContext.Current khi đang ở trên chuỗi giao diện người dùng. Lưu điều đó cho sau này, khi bạn đang ở trên một chủ đề khác. –

2

Đầu tiên, trong hàm tạo biểu mẫu của bạn, hãy giữ tham chiếu có phạm vi lớp đến đối tượng SynchronizationContext.Current (thực tế là một WindowsFormsSynchronizationContext).

public partial class MyForm : Form { 
    private SynchronizationContext syncContext; 
    public MyForm() { 
     this.syncContext = SynchronizationContext.Current; 
    } 
} 

Sau đó, bất cứ nơi nào trong lớp học của bạn, sử dụng bối cảnh này để gửi tin nhắn cho giao diện người dùng:

public partial class MyForm : Form { 
    public void DoStuff() { 
     ThreadPool.QueueUserWorkItem(_ => { 
      // worker thread starts 
      // invoke UI from here 
      this.syncContext.Send(() => 
       this.myButton.Text = "Updated from worker thread"); 
      // continue background work 
      this.syncContext.Send(() => { 
       this.myText1.Text = "Updated from worker thread"; 
       this.myText2.Text = "Updated from worker thread"; 
      }); 
      // continue background work 
     }); 
    } 
} 

Bạn sẽ cần các phương pháp khuyến nông sau đây để làm việc với các biểu thức lambda: http://codepaste.net/zje4k6