Câu hỏi Làm cách nào để cập nhật GUI từ một chuỗi khác?


Cách đơn giản nhất để cập nhật Label từ một chủ đề khác?

tôi có một Form trên thread1và từ đó tôi bắt đầu một chuỗi khác (thread2). Trong khi thread2 đang xử lý một số tệp tôi muốn cập nhật Label trên Form với trạng thái hiện tại của thread2công việc.

Làm thế nào tôi có thể làm điều đó?


1143
2018-03-19 09:37


gốc


Không .net 2.0+ có lớp BackgroundWorker chỉ dành cho việc này. Nó nhận biết thread UI. 1. Tạo một BackgroundWorker 2. Thêm hai đại biểu (một cho xử lý, và một cho hoàn thành) - Preet Sangha
có thể hơi muộn: codeproject.com/KB/cs/Threadsafe_formupdating.aspx - MichaelD
Xem câu trả lời cho .NET 4.5 và C # 5.0: stackoverflow.com/a/18033198/2042090 - Ryszard Dżegan
Câu hỏi này không áp dụng cho Gtk # GUI. Đối với Gtk # xem điều này và điều này câu trả lời. - hlovdal
Hãy coi chừng: câu trả lời cho câu hỏi này giờ đây là một mớ hỗn độn lộn xộn của OT ("đây là những gì tôi đã làm cho ứng dụng WPF của tôi") và các tạo tác .NET 2.0 lịch sử. - Marc L.


Các câu trả lời:


Đối với .NET 2.0, đây là một đoạn mã đẹp mà tôi đã viết, thực hiện chính xác những gì bạn muốn và làm việc cho bất kỳ thuộc tính nào trên một Control:

private delegate void SetControlPropertyThreadSafeDelegate(
    Control control, 
    string propertyName, 
    object propertyValue);

public static void SetControlPropertyThreadSafe(
    Control control, 
    string propertyName, 
    object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
        propertyName, 
        BindingFlags.SetProperty, 
        null, 
        control, 
        new object[] { propertyValue });
  }
}

Gọi nó như thế này:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

Nếu bạn đang sử dụng .NET 3.0 hoặc cao hơn, bạn có thể viết lại phương thức trên làm phương thức mở rộng của Control lớp học, sau đó sẽ đơn giản hóa cuộc gọi đến:

myLabel.SetPropertyThreadSafe("Text", status);

CẬP NHẬT 05/10/2010:

Đối với .NET 3.0, bạn nên sử dụng mã này:

private delegate void SetPropertyThreadSafeDelegate<TResult>(
    Control @this, 
    Expression<Func<TResult>> property, 
    TResult value);

public static void SetPropertyThreadSafe<TResult>(
    this Control @this, 
    Expression<Func<TResult>> property, 
    TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
      as PropertyInfo;

  if (propertyInfo == null ||
      !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
          propertyInfo.Name, 
          propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
      @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
      (SetPropertyThreadSafe), 
      new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
          propertyInfo.Name, 
          BindingFlags.SetProperty, 
          null, 
          @this, 
          new object[] { value });
  }
}

sử dụng các biểu thức LINQ và lambda để cho phép cú pháp sạch hơn, đơn giản hơn và an toàn hơn:

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

Không chỉ là tên thuộc tính bây giờ được kiểm tra tại thời gian biên dịch, loại thuộc tính là tốt, vì vậy nó không thể (ví dụ) gán một giá trị chuỗi cho một thuộc tính boolean, và do đó gây ra một ngoại lệ thời gian chạy.

Thật không may điều này không ngăn cản bất cứ ai làm những điều ngu ngốc như đi qua một thứ khác Control's tài sản và giá trị, do đó, sau đây sẽ vui vẻ biên dịch:

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

Do đó tôi đã thêm kiểm tra thời gian chạy để đảm bảo rằng thuộc tính được chuyển đổi thực sự thuộc về Control rằng phương thức đang được gọi. Không hoàn hảo, nhưng vẫn tốt hơn rất nhiều so với phiên bản .NET 2.0.

Nếu bất kỳ ai có thêm bất kỳ đề xuất nào về cách cải thiện mã này để đảm bảo an toàn cho thời gian biên dịch, hãy bình luận!


687
2018-03-19 10:37



Có trường hợp khi this.GetType () đánh giá giống như propertyInfo.ReflectedType (ví dụ LinkLabel trên WinForms). Tôi không có một kinh nghiệm C # lớn, nhưng tôi nghĩ rằng điều kiện cho ngoại lệ nên là: if (propertyInfo == null || (!@this.GetType (). IsSubclassOf (propertyInfo.ReflectedType) && @ this.GetType ( )! = propertyInfo.ReflectedType) || @ this.GetType (). GetProperty (thuộc tínhInfo.Name, propertyInfo.PropertyType) == null) - Corvin
@lan có thể SetControlPropertyThreadSafe(myLabel, "Text", status) được gọi từ một mô-đun hoặc lớp hoặc biểu mẫu khác - Smith
Giải pháp được cung cấp là phức tạp không cần thiết. Xem giải pháp của Marc Gravell, hoặc giải pháp của Zaid Masud, nếu bạn coi trọng sự đơn giản. - Frank Hileman
Giải pháp này làm lãng phí tài nguyên của tấn nếu bạn cập nhật nhiều thuộc tính vì mỗi Invoke đều tốn rất nhiều tài nguyên. Tôi không nghĩ rằng đây là cách tính năng của Thread Safety được dự định. Làm Encapsulte hành động cập nhật giao diện người dùng của bạn và Gọi nó là ONCE (và không phải cho mỗi thuộc tính) - Console
Tại sao bạn sử dụng mã này trên thành phần BackgroundWorker? - Andy


Các đơn giản nhất cách là một phương thức nặc danh được chuyển vào Label.Invoke:

// Running on the worker thread
string newText = "abc";
form.Label.Invoke((MethodInvoker)delegate {
    // Running on the UI thread
    form.Label.Text = newText;
});
// Back on the worker thread

Thông báo rằng Invoke khối thực hiện cho đến khi nó hoàn thành - đây là mã đồng bộ. Câu hỏi không hỏi về mã không đồng bộ, nhưng có rất nhiều nội dung trên Stack Overflow về cách viết mã không đồng bộ khi bạn muốn tìm hiểu về nó.


938
2018-03-19 10:17



Nhìn thấy như OP đã không đề cập đến bất kỳ lớp / dụ ngoại trừ biểu mẫu, đó không phải là một mặc định xấu ... - Marc Gravell♦
Đừng quên từ khóa "this" đang tham chiếu đến lớp "Control". - AZ.
@codecompleting nó là an toàn hoặc cách, và chúng tôi đã biết chúng tôi đang ở trên một công nhân, vậy tại sao kiểm tra một cái gì đó chúng ta biết? - Marc Gravell♦
@ Dragouf không thực sự - một trong những điểm của việc sử dụng phương pháp này là bạn đã biết phần nào chạy trên công nhân và chạy trên thread UI. Không cần kiểm tra. - Marc Gravell♦
@ Joan.bdm không có nơi nào đủ gần ngữ cảnh để tôi bình luận về điều đó - Marc Gravell♦


Xử lý công việc lâu dài

.NET 4.5 và C # 5.0 bạn nên sử dụng Mẫu không đồng bộ dựa trên nhiệm vụ (TAP) cùng với không đồng bộ- -đang chờ từ khóa ở mọi khu vực (bao gồm GUI):

TAP là mẫu thiết kế không đồng bộ được đề xuất cho phát triển mới

thay vì Mô hình lập trình không đồng bộ (APM) và Mẫu không đồng bộ dựa trên sự kiện (EAP) (sau này bao gồm Lớp BackgroundWorker).

Sau đó, giải pháp được đề xuất cho phát triển mới là:

  1. Thực hiện không đồng bộ một trình xử lý sự kiện (Có, đó là tất cả):

    private async void Button_Clicked(object sender, EventArgs e)
    {
        var progress = new Progress<string>(s => label.Text = s);
        await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress),
                                    TaskCreationOptions.LongRunning);
        label.Text = "completed";
    }
    
  2. Thực hiện chuỗi thứ hai để thông báo cho luồng giao diện người dùng:

    class SecondThreadConcern
    {
        public static void LongWork(IProgress<string> progress)
        {
            // Perform a long running work...
            for (var i = 0; i < 10; i++)
            {
                Task.Delay(500).Wait();
                progress.Report(i.ToString());
            }
        }
    }
    

Lưu ý những điều sau:

  1. Mã ngắn và sạch được viết theo cách tuần tự mà không cần gọi lại và các chủ đề rõ ràng.
  2. Bài tập thay vì Chủ đề.
  3. không đồng bộ từ khóa, cho phép sử dụng đang chờ do đó ngăn cản trình xử lý sự kiện đạt trạng thái hoàn thành cho đến khi tác vụ kết thúc và trong khi chờ đợi không chặn luồng giao diện người dùng.
  4. Lớp tiến độ (xem Giao diện IProgress) hỗ trợ Tách mối quan tâm (SoC) nguyên tắc thiết kế và không yêu cầu người điều phối rõ ràng và gọi. Nó sử dụng dòng điện SynchronizationContext từ nơi sáng tạo của nó (ở đây là chuỗi giao diện người dùng).
  5. TaskCreationOptions.LongRunning gợi ý không xếp hàng nhiệm vụ vào ThreadPool.

Để biết ví dụ chi tiết hơn, hãy xem: Tương lai của C #: Những điều tốt đẹp đến với những người đang chờ đợi bởi Joseph Albahari.

Xem thêm về UI Threading Model khái niệm.

Xử lý ngoại lệ

Đoạn mã dưới đây là ví dụ về cách xử lý các ngoại lệ và nút chuyển đổi Enabled tài sản để ngăn chặn nhiều nhấp chuột trong quá trình thực thi nền.

private async void Button_Click(object sender, EventArgs e)
{
    button.Enabled = false;

    try
    {
        var progress = new Progress<string>(s => button.Text = s);
        await Task.Run(() => SecondThreadConcern.FailingWork(progress));
        button.Text = "Completed";
    }
    catch(Exception exception)
    {
        button.Text = "Failed: " + exception.Message;
    }

    button.Enabled = true;
}

class SecondThreadConcern
{
    public static void FailingWork(IProgress<string> progress)
    {
        progress.Report("I will fail in...");
        Task.Delay(500).Wait();

        for (var i = 0; i < 3; i++)
        {
            progress.Report((3 - i).ToString());
            Task.Delay(500).Wait();
        }

        throw new Exception("Oops...");
    }
}

334
2017-08-03 13:09



Nếu SecondThreadConcern.LongWork() ném một ngoại lệ, nó có thể bị bắt bởi thread UI? Đây là một bài đăng tuyệt vời, btw. - kdbanman
Tôi đã thêm phần bổ sung vào câu trả lời để đáp ứng các yêu cầu của bạn. Trân trọng. - Ryszard Dżegan
Các Lớp ExceptionDispatchInfo chịu trách nhiệm cho phép lạ của việc sửa lại ngoại lệ nền trên chuỗi giao diện người dùng trong mẫu chờ đợi không đồng bộ. - Ryszard Dżegan
Có phải chỉ là tôi nghĩ rằng cách này để làm điều này là tiết tú hơn là chỉ gọi Invoke / Begin ?! - Marco
Task.Delay(500).Wait()? Điểm tạo một Task là gì để chặn luồng hiện tại? Bạn không bao giờ nên chặn một thread thread thread! - Yarik


Biến thể của Marc Gravell's đơn giản nhất dung dịch cho .NET 4:

control.Invoke((MethodInvoker) (() => control.Text = "new text"));

Hoặc sử dụng Tác vụ đại diện thay thế:

control.Invoke(new Action(() => control.Text = "new text"));

Xem ở đây để so sánh cả hai: MethodInvoker vs Action for Control.BeginInvoke


195
2018-05-29 18:51



'kiểm soát' trong ví dụ này là gì? Kiểm soát giao diện người dùng của tôi? Cố gắng thực hiện điều này trong WPF trên một điều khiển nhãn, và Invoke không phải là thành viên của nhãn của tôi. - Dbloom
Còn về phương pháp mở rộng như @styxriver stackoverflow.com/a/3588137/206730 ? - Kiquenet
khai báo 'Action y;' bên trong lớp hoặc phương pháp thay đổi thuộc tính văn bản và cập nhật văn bản với đoạn mã này 'yourcontrol.Invoke (y = () => yourcontrol.Text = "new text");' - Antonio Leite
@ Dbloom nó không phải là thành viên vì nó chỉ dành cho WinForms. Đối với WPF bạn sử dụng Dispatcher.Invoke - sLw
Tôi đã làm theo giải pháp này nhưng đôi khi giao diện người dùng của tôi không được cập nhật. Tôi thấy rằng tôi cần this.refresh() để buộc vô hiệu hóa và vẽ lại GUI .. nếu nó hữu ích .. - Rakibul Haq


Lửa và quên phương pháp mở rộng cho .NET 3.5+

using System;
using System.Windows.Forms;

public static class ControlExtensions
{
    /// <summary>
    /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
    /// </summary>
    /// <param name="control"></param>
    /// <param name="code"></param>
    public static void UIThread(this Control @this, Action code)
    {
        if (@this.InvokeRequired)
        {
            @this.BeginInvoke(code);
        }
        else
        {
            code.Invoke();
        }
    }
}

Điều này có thể được gọi bằng cách sử dụng dòng mã sau đây:

this.UIThread(() => this.myLabel.Text = "Text Goes Here");

115
2017-08-27 21:10



Điểm của việc sử dụng này là gì? Sẽ không "kiểm soát" được tương đương? Có lợi ích gì cho @this không? - jeromeyers
@jeromeyers - The @this chỉ đơn giản là tên biến, trong trường hợp này tham chiếu tới điều khiển hiện tại đang gọi phần mở rộng. Bạn có thể đổi tên nó thành nguồn, hoặc bất cứ điều gì nổi thuyền của bạn. tôi sử dụng @this, vì nó đề cập đến 'Điều khiển này' đang gọi phần mở rộng và nhất quán (trong đầu tôi, ít nhất) với việc sử dụng từ khoá 'này' trong mã bình thường (không mở rộng). - StyxRiver
Điều này thật tuyệt vời, dễ dàng và cho tôi giải pháp tốt nhất. Bạn có thể bao gồm tất cả công việc bạn phải làm trong chuỗi ui. Ví dụ: this.UIThread (() => {txtMessage.Text = message; listBox1.Items.Add (message);}); - Auto
Tôi thực sự thích giải pháp này. Nit nhỏ: Tôi sẽ đặt tên cho phương pháp này OnUIThread thay vì UIThread. - ToolmakerSteve
@ToolmakerSteve - Lý do duy nhất tôi chọn không phải sử dụng OnXXX phong cách là bởi vì đó thường chỉ ra một sự kiện quá tải. OnLoad, OnInitialize, vv Có lẽ không phải là quyết định tốt nhất, nhưng đó là những gì tôi đã có trong tâm trí vào thời điểm đó. Và wow, đã gần 6 năm trước rồi. Thời gian trôi qua. - StyxRiver


Đây là cách cổ điển bạn nên làm điều này:

using System;
using System.Windows.Forms;
using System.Threading;

namespace Test
{
    public partial class UIThread : Form
    {
        Worker worker;

        Thread workerThread;

        public UIThread()
        {
            InitializeComponent();

            worker = new Worker();
            worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
            workerThread = new Thread(new ThreadStart(worker.StartWork));
            workerThread.Start();
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
        {
            // Cross thread - so you don't get the cross-threading exception
            if (this.InvokeRequired)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    OnWorkerProgressChanged(sender, e);
                });
                return;
            }

            // Change control
            this.label1.Text = e.Progress;
        }
    }

    public class Worker
    {
        public event EventHandler<ProgressChangedArgs> ProgressChanged;

        protected void OnProgressChanged(ProgressChangedArgs e)
        {
            if(ProgressChanged!=null)
            {
                ProgressChanged(this,e);
            }
        }

        public void StartWork()
        {
            Thread.Sleep(100);
            OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
            Thread.Sleep(100);
        }
    }


    public class ProgressChangedArgs : EventArgs
    {
        public string Progress {get;private set;}
        public ProgressChangedArgs(string progress)
        {
            Progress = progress;
        }
    }
}

Chuỗi công nhân của bạn có sự kiện. Chuỗi giao diện người dùng của bạn bắt đầu một chuỗi khác để thực hiện công việc và móc nối sự kiện công nhân đó để bạn có thể hiển thị trạng thái của chuỗi công việc.

Sau đó, trong giao diện người dùng, bạn cần phải vượt qua các chủ đề để thay đổi điều khiển thực tế ... như nhãn hoặc thanh tiến trình.


57
2018-03-19 10:31





Giải pháp đơn giản là sử dụng Control.Invoke.

void DoSomething()
{
    if (InvokeRequired) {
        Invoke(new MethodInvoker(updateGUI));
    } else {
        // Do Something
        updateGUI();
    }
}

void updateGUI() {
    // update gui here
}

51
2018-03-19 09:46



Nó không hoàn hảo. khi cập nhật bất kỳ điều khiển nào, điều khiển khác không phản hồi .. - Ahosan Karim Asik
cũng được thực hiện cho sự đơn giản! không chỉ đơn giản, mà còn hoạt động tốt! Tôi thực sự không hiểu tại sao microsoft không thể làm cho nó đơn giản như nó có nghĩa là phải! để gọi 1 dòng trên sợi chính, chúng ta nên viết vài chức năng! - MBH
@MBH Đồng ý. BTW, bạn có chú ý không stackoverflow.com/a/3588137/199364 câu trả lời ở trên, định nghĩa phương thức mở rộng? Làm điều đó một lần trong một lớp tiện ích tùy chỉnh, sau đó không phải quan tâm nữa mà Microsoft đã không làm điều đó cho chúng tôi :) - ToolmakerSteve
@ToolmakerSteve Thats chính xác những gì nó có nghĩa là! bạn đúng, chúng tôi có thể tìm ra cách, nhưng ý tôi là từ DRY (đừng lặp lại) quan điểm, vấn đề có giải pháp chung, có thể được giải quyết bởi chúng với nỗ lực tối thiểu của Microsoft sẽ tiết kiệm rất nhiều thời gian cho lập trình viên :) - MBH


Mã luồng thường là lỗi và luôn khó kiểm tra. Bạn không cần viết mã luồng để cập nhật giao diện người dùng từ tác vụ nền. Chỉ cần sử dụng BackgroundWorker lớp để chạy tác vụ và ReportProgress để cập nhật giao diện người dùng. Thông thường, bạn chỉ cần báo cáo một phần trăm hoàn thành, nhưng có một tình trạng quá tải khác bao gồm một đối tượng trạng thái. Dưới đây là một ví dụ chỉ báo cáo một đối tượng chuỗi:

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "A");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "B");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "C");
    }

    private void backgroundWorker1_ProgressChanged(
        object sender, 
        ProgressChangedEventArgs e)
    {
        label1.Text = e.UserState.ToString();
    }

Đó là tốt nếu bạn luôn luôn muốn cập nhật cùng một lĩnh vực. Nếu bạn có các bản cập nhật phức tạp hơn để thực hiện, bạn có thể định nghĩa một lớp để biểu diễn trạng thái UI và chuyển nó tới phương thức ReportProgress.

Một điều cuối cùng, hãy chắc chắn đặt WorkerReportsProgress cờ hoặc ReportProgress phương thức sẽ bị bỏ qua hoàn toàn.


41
2017-08-27 20:42



Khi kết thúc quá trình xử lý, bạn cũng có thể cập nhật giao diện người dùng qua backgroundWorker1_RunWorkerCompleted. - DavidRR


Phần lớn các câu trả lời sử dụng Control.Invoke mà là một tình trạng chạy đua chờ đợi xảy ra. Ví dụ: xem xét câu trả lời được chấp nhận:

string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate { 
    someLabel.Text = newText; // runs on UI thread
});

Nếu người dùng đóng biểu mẫu ngay trước this.Invoke được gọi (nhớ, this là Form đối tượng), một ObjectDisposedException sẽ có khả năng bị sa thải.

Giải pháp là sử dụng SynchronizationContext, đặc biệt SynchronizationContext.Current như hamilton.danielb gợi ý (các câu trả lời khác dựa vào cụ thể SynchronizationContext triển khai hoàn toàn không cần thiết). Tôi sẽ sửa đổi một chút mã của anh ấy để sử dụng SynchronizationContext.Post thay vì SynchronizationContext.Send mặc dù (vì thường không cần chuỗi công nhân đợi):

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

    private MethodOnOtherThread()
    {
         ...
         _context.Post(status => someLabel.Text = newText,null);
    }
}

Lưu ý rằng trên .NET 4.0 trở lên, bạn thực sự nên sử dụng các tác vụ cho các hoạt động async. Xem n-san's câu trả lời cho phương pháp dựa trên nhiệm vụ tương đương (sử dụng TaskScheduler.FromCurrentSynchronizationContext).

Cuối cùng, trên .NET 4.5 trở lên, bạn cũng có thể sử dụng Progress<T> (mà về cơ bản chụp SynchronizationContext.Current khi nó được tạo ra) như được minh chứng bởi Ryszard Dżegan's trong trường hợp hoạt động lâu dài cần chạy mã UI trong khi vẫn hoạt động.


31
2018-05-24 08:45





Bạn sẽ phải đảm bảo rằng bản cập nhật diễn ra đúng chủ đề; chuỗi giao diện người dùng.

Để thực hiện điều này, bạn sẽ phải gọi trình xử lý sự kiện thay vì gọi trực tiếp.

Bạn có thể làm điều này bằng cách tăng sự kiện của bạn như thế này:

(Mã được gõ ở đây ra khỏi đầu của tôi, vì vậy tôi đã không kiểm tra đúng cú pháp, vv, nhưng nó sẽ giúp bạn đi.)

if( MyEvent != null )
{
   Delegate[] eventHandlers = MyEvent.GetInvocationList();

   foreach( Delegate d in eventHandlers )
   {
      // Check whether the target of the delegate implements 
      // ISynchronizeInvoke (Winforms controls do), and see
      // if a context-switch is required.
      ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;

      if( target != null && target.InvokeRequired )
      {
         target.Invoke (d, ... );
      }
      else
      {
          d.DynamicInvoke ( ... );
      }
   }
}

Lưu ý rằng mã ở trên sẽ không hoạt động trên các dự án WPF, vì các điều khiển WPF không thực hiện ISynchronizeInvoke giao diện.

Để đảm bảo rằng mã ở trên hoạt động với Windows Forms và WPF và tất cả các nền tảng khác, bạn có thể xem AsyncOperation, AsyncOperationManager và SynchronizationContext các lớp học.

Để dễ dàng nâng cao sự kiện theo cách này, tôi đã tạo một phương thức mở rộng, cho phép tôi đơn giản hóa việc nâng cao sự kiện bằng cách chỉ cần gọi:

MyEvent.Raise(this, EventArgs.Empty);

Tất nhiên, bạn cũng có thể sử dụng lớp BackGroundWorker, điều này sẽ trừu tượng vấn đề này cho bạn.


30
2018-03-19 09:45



Thật vậy, nhưng tôi không thích 'lộn xộn' mã GUI của tôi với vấn đề này. GUI của tôi không nên quan tâm cho dù nó cần phải gọi hay không. Nói cách khác: tôi không nghĩ rằng đó là trách nhiệm của GUI để thực hiện bối cảnh-swithc. - Frederik Gheysels
Phá vỡ các đại biểu ngoài vv dường như quá mức cần thiết - tại sao không chỉ: SynchronizationContext.Current.Send (delegate {MyEvent (...);}, null); - Marc Gravell♦
Bạn luôn có quyền truy cập vào SynchronizationContext? Ngay cả khi lớp của bạn đang ở trong một lớp học lib? - Frederik Gheysels