Thay đổi các điều khiển WPF từ một chuỗi không phải chính bằng cách sử dụng Dispatcher.


81

Gần đây tôi đã bắt đầu lập trình trong WPF và gặp sự cố sau. Tôi không hiểu cách sử dụng Dispatcher.Invoke()phương pháp. Tôi có kinh nghiệm về phân luồng và tôi đã tạo một vài chương trình Windows Forms đơn giản mà tôi vừa sử dụng

Control.CheckForIllegalCrossThreadCalls = false;

Vâng, tôi biết điều đó khá khập khiễng nhưng đây là những ứng dụng giám sát đơn giản.

Thực tế là bây giờ tôi đang tạo một ứng dụng WPF lấy dữ liệu trong nền, tôi bắt đầu một luồng mới để thực hiện lệnh gọi truy xuất dữ liệu (từ máy chủ web), bây giờ tôi muốn hiển thị nó trên biểu mẫu WPF của mình. Vấn đề là, tôi không thể thiết lập bất kỳ kiểm soát nào từ luồng này. Thậm chí không có nhãn hay bất cứ thứ gì. Vấn đề đó được giải quyết như thế nào?

Trả lời nhận xét:
@Jalfp:
Vì vậy, tôi sử dụng phương pháp Điều phối này trong 'bước đi mới' khi tôi nhận được dữ liệu? Hay tôi nên làm cho một nhân viên chạy nền truy xuất dữ liệu, đặt nó vào một trường và bắt đầu một chuỗi mới đợi cho đến khi trường này được lấp đầy và gọi người điều phối để hiển thị dữ liệu đã truy xuất vào các điều khiển?


CheckForIllegalCrossThreadCalls đó thật tuyệt vời. Ước gì tôi biết điều đó trước đó để có các ứng dụng "ai quan tâm" nhanh chóng
Gaspa79

Câu trả lời:


177

Điều đầu tiên là phải hiểu rằng, Dispatcher không được thiết kế để chạy hoạt động chặn lâu dài (chẳng hạn như lấy dữ liệu từ WebServer ...). Bạn có thể sử dụng Điều phối viên khi bạn muốn chạy một hoạt động sẽ được thực thi trên chuỗi giao diện người dùng (chẳng hạn như cập nhật giá trị của thanh tiến trình).

Những gì bạn có thể làm là truy xuất dữ liệu của mình trong một công nhân nền và sử dụng phương thức ReportProgress để phổ biến các thay đổi trong chuỗi giao diện người dùng.

Nếu bạn thực sự cần sử dụng trực tiếp Dispatcher, nó khá đơn giản:

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => this.progressBar.Value = 50));

22
Bạn có thể thoát khỏi 'Action mới (' một phần, và chỉ cần sử dụng một biểu thức lambda: DispatcherPriority.Background, () => this.progressBar.Value = 50
jrista

Ừ không biết tại sao tôi đặt một hành động ở đây: p
japf

1
@Carsten Câu trả lời này dành cho các ứng dụng WPF sử dụng lớp System.Windows.Application.
joshuapoehls

10
@jrista: Có thật không? Tôi nhận được CS1660 khi thử mà không có new Action(...).
HOẶC Người lập bản đồ

6
@jrista: Nói chung, đúng - mặc dù bài viết này giải thích lý do tại sao nó không hoạt động trong trường hợp các phương thức không có tham số như các phương thức được truyền đến BeginInvokevà thay vào đó là lỗi trình biên dịch CS1660.
HOẶC Người lập bản đồ

31

japf đã trả lời nó một cách chính xác. Trong trường hợp nếu bạn đang xem xét các hành động nhiều dòng, bạn có thể viết như dưới đây.

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => { 
    this.progressBar.Value = 50;
  }));

Thông tin cho những người dùng khác muốn biết về hiệu suất:

Nếu mã của bạn CẦN được viết để đạt hiệu suất cao, trước tiên bạn có thể kiểm tra xem lệnh gọi có được yêu cầu hay không bằng cách sử dụng cờ CheckAccess.

if(Application.Current.Dispatcher.CheckAccess())
{
    this.progressBar.Value = 50;
}
else
{
    Application.Current.Dispatcher.BeginInvoke(
      DispatcherPriority.Background,
      new Action(() => { 
        this.progressBar.Value = 50;
      }));
}

Lưu ý rằng phương thức CheckAccess () bị ẩn khỏi Visual Studio 2015, vì vậy chỉ cần viết nó mà không mong đợi intellisense hiển thị nó. Lưu ý rằng CheckAccess có chi phí về hiệu suất (chi phí trong vài nano giây). Nó chỉ tốt hơn khi bạn muốn tiết kiệm micro giây đó để thực hiện 'lệnh gọi' bằng bất kỳ giá nào. Ngoài ra, luôn có tùy chọn để tạo hai phương thức (bật với lệnh gọi và phương thức khác không có) khi phương thức gọi có chắc chắn trong UI Thread hay không. Đây chỉ là trường hợp hiếm hoi nhất khi bạn nên xem xét khía cạnh này của người điều phối.


4

Khi một luồng đang thực thi và bạn muốn thực thi luồng giao diện người dùng chính bị chặn bởi luồng hiện tại, hãy sử dụng như sau:

chủ đề hiện tại:

Dispatcher.CurrentDispatcher.Invoke(MethodName,
    new object[] { parameter1, parameter2 }); // if passing 2 parameters to method.

Chuỗi giao diện người dùng chính:

Application.Current.Dispatcher.BeginInvoke(
    DispatcherPriority.Background, new Action(() => MethodName(parameter)));

MethodName không tồn tại trong bối cảnh hiện tại
AriesConnolly

0

Câu trả lời @japf ở trên đang hoạt động tốt và trong trường hợp của tôi, tôi muốn thay đổi con trỏ chuột từ Bánh xe quay trở lại Mũi tên bình thường sau khi Trình duyệt CEF tải xong trang. Trong trường hợp nó có thể giúp ai đó, đây là mã:

private void Browser_LoadingStateChanged(object sender, CefSharp.LoadingStateChangedEventArgs e) {
   if (!e.IsLoading) {
      // set the cursor back to arrow
      Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
         new Action(() => Mouse.OverrideCursor = Cursors.Arrow));
   }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.