2012-03-12 38 views
10

Tôi có một ứng dụng winforms đơn giản, thực hiện quy trình chạy dài trên một luồng khác thông qua một nhiệm vụ TPL. Trong quá trình chạy dài này, tôi muốn cập nhật giao diện người dùng (thanh tiến trình hoặc thứ gì đó). Có cách nào để làm điều này mà không bị yêu cầu .ContinueWith()?Cách cập nhật giao diện người dùng từ các tác vụ con trong WinForms

public partial class Form1 : Form 
{ 
    private Task _childTask; 

    public Form1() 
    { 
     InitializeComponent(); 

     Task.Factory.StartNew(() => 
     { 
      // Do some work 
      Thread.Sleep(1000); 

      // Update the UI 
      _childTask.Start(); 

      // Do more work 
      Thread.Sleep(1000); 
     }); 

     _childTask = new Task((antecedent) => 
     { 
      Thread.Sleep(2000); 
      textBox1.Text = "From child task"; 
     }, TaskScheduler.FromCurrentSynchronizationContext()); 


    } 
} 

Thực thi mã này tôi nhận được ngoại lệ phổ biến:

Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.

+0

Tôi nghĩ bạn phải dán tham chiếu hộp văn bản vào chuỗi. – jwillmer

Trả lời

10

Vâng, bạn có thể dứt khoát gọi BeginInvoke trên Window/Control mà bạn muốn giao tiếp với. Trong trường hợp của bạn này sẽ trông như thế này:

this.textBox.BeginInvoke(new Action(() => 
{ 
    this.textBox.Text = "From child task."; 
})); 
+0

Điều đó chắc chắn có vẻ như đang trên đường nhưng tôi nhận được lỗi biên dịch: Không thể chuyển đổi biểu thức lambda thành loại 'System.Delegate' vì nó không phải là loại đại biểu – devlife

+0

Tôi đã tạo một Hành động và chuyển vào hành động thay vì phương thức ẩn danh. Không chắc tại sao những điều trên không hiệu quả nhưng nó giúp tôi đạt được 99% con đường ở đó. Cảm ơn Drew. – devlife

+1

@devlife Rất tiếc, đó là lỗi của tôi khi được sử dụng để làm việc với các API mới hơn. Có, bạn cần phải rõ ràng bọc với một hành động mới (...) vì cách chữ ký của WinForms BeginInvoke API cũ được thiết kế. Tôi sẽ cập nhật câu trả lời của mình. –

5

Bạn đang đi qua các TaskScheduler như state (hoặc antecedent, như bạn gọi nó). Điều đó không có ý nghĩa nhiều.

Tôi không chắc chắn chính xác bạn muốn làm gì, nhưng bạn cần chỉ định TaskScheduler khi bạn bắt đầu Task, không phải khi bạn đang tạo. Ngoài ra, có vẻ như childTask không phải là một lĩnh vực:

var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

Task childTask = null; 

Task.Factory.StartNew(
    () => 
    { 
     Thread.Sleep(1000); 
     childTask.Start(scheduler); 
     Thread.Sleep(1000); 
    }); 

childTask = new Task(
    () => 
    { 
     Thread.Sleep(2000); 
     textBox1.Text = "From child task"; 
    }); 

Tất nhiên, tất cả điều này sẽ dễ dàng hơn nhiều với C# 5 và await:

public Form1() 
{ 
    InitializeComponent(); 

    StartAsync(); 
} 

private async void StartAsync() 
{ 
    // do some work 
    await Task.Run(() => { Thread.Sleep(1000); }); 

    // start more work 
    var moreWork = Task.Run(() => { Thread.Sleep(1000); }); 

    // update the UI, based on data from “some work” 
    textBox1.Text = "From async method"; 

    // wait until “more work” finishes 
    await moreWork; 
} 

Bạn không thể hãy tạo phương thức khởi tạo async nhưng bạn có thể chạy phương thức async từ nó. “Làm việc” không chạy trên chuỗi giao diện người dùng, vì nó được bắt đầu một cách rõ ràng thông qua Task.Run(). Nhưng kể từ khi StartAsync() được gọi trực tiếp, nó thực thi trên chuỗi giao diện người dùng.

+0

Đó chỉ là vấn đề. Tôi không muốn đợi cho đến khi một nhiệm vụ được hoàn thành để thực hiện lệnh còn lại. – devlife

+0

Tôi đã cập nhật ví dụ của mình để hiển thị tốt hơn những gì tôi đang cố gắng vượt qua. – devlife

+0

Trong trường hợp đó, mẫu đầu tiên của tôi sẽ vẫn hoạt động cho bạn. Tôi cũng đã cập nhật phiên bản C# 5. – svick

Các vấn đề liên quan