Làm cách nào để cập nhật GUI từ luồng khác?


1393

Đó là cách đơn giản nhất để cập nhật Labeltừ người khác Thread?

  • Tôi Formđang chạy thread1và từ đó tôi bắt đầu một chủ đề khác ( thread2).

  • Trong khi thread2đang xử lý một số tác phẩm tôi muốn cập nhật một Labeltrên Formvới hiện trạng thread2của công việc.

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


25
Không .net 2.0+ có lớp BackgroundWorker chỉ cho việc này. Nó nhận biết chủ đề UI. 1. Tạo một BackgroundWorker 2. Thêm hai đại biểu (một để xử lý và một để hoàn thành)
Preet Sangha


4
Xem câu trả lời cho .NET 4.5 và C # 5.0: stackoverflow.com/a/18033198/2042090
Ryszard Dżegan

5
Câu hỏi này không áp dụng cho Gtk # GUI. Đối với Gtk # xem điều này và câu trả lời này .
hlovdal

Coi chừng: các câu trả lời cho câu hỏi này hiện là một mớ hỗn độn của OT ("đây là những gì tôi đã làm cho ứng dụng WPF của mình") và các tạo tác .NET 2.0 lịch sử.
Marc L.

Câu trả lời:


768

Đối với .NET 2.0, đây là một đoạn mã đẹp mà tôi đã viết chính xác những gì bạn muốn và hoạt động cho bất kỳ thuộc tính nào trên 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 trở lên, bạn có thể viết lại phương thức trên dưới dạng phương thức mở rộng của Controllớp, 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 });
  }
}

trong đó 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

Bây giờ, không chỉ tên thuộc tính được kiểm tra tại thời gian biên dịch, loại thuộc tính cũng vậy, do đó, không thể (ví dụ) gán giá trị chuỗi cho thuộc tính boolean và do đó gây ra 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ư chuyển vào Controltài sản và giá trị của người khác, vì vậy những điều sau đây sẽ vui vẻ biên dịch:

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

Do đó tôi đã thêm các kiểm tra thời gian chạy để đảm bảo rằng thuộc tính được truyền thực sự thuộc về Controlphươ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 cứ ai có bất kỳ đề xuất nào thêm về cách cải thiện mã này để đảm bảo an toàn trong thời gian biên dịch, vui lòng bình luận!


3
Có những trường hợp khi this.GetType () ước tính giống như propertyInfo.ReflectedType (ví dụ: LinkLabel trên WinForms). Tôi không có trải nghiệm C # lớn, nhưng tôi nghĩ rằng điều kiện cho ngoại lệ phải là: if (propertyInfo == null || (!@this.GetType (). IsSubgroupOf (propertyInfo.ReflectedType) && @ this.GetType ( )! = propertyInfo.ReflectedType) || @ this.GetType (). GetProperty (propertyInfo.Name, propertyInfo.PropertyType) == null)
Corvin

9
@lan điều này 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

71
Các 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

8
Giải pháp này sẽ lãng phí hàng tấn tài nguyên nếu bạn cập nhật nhiều thuộc tính vì mỗi lần gọi sẽ tốn rất nhiều tài nguyên. Tôi không nghĩ đây là cách tính năng của Thread Safety được dự định. Thực hiện Encapsulte các hành động cập nhật giao diện người dùng của bạn và gọi nó ONCE (và không phải cho mỗi thuộc tính)
Bảng điều khiển

4
Tại sao bạn lại sử dụng mã này trên thành phần BackgroundWorker?
Andy

1080

Cách đơn giản nhất là một phương thức ẩn danh được truyề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

Lưu ý rằng Invokekhối thực thi cho đến khi 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ó.


8
Xem như OP đã không đề cập đến bất kỳ lớp / thể hiện nào ngoại trừ biểu mẫu, đó không phải là một mặc định xấu ...
Marc Gravell

39
Đừng quên từ khóa "này" đang tham khảo lớp "Kiểm soát".
AZ.

8
@codecompleting nó cũng an toàn, và chúng tôi đã biết chúng tôi là một công nhân, vậy tại sao phải kiểm tra một cái gì đó chúng tôi biết?
Marc Gravell

4
@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 worker và phần nào chạy trên luồng UI. Không cần kiểm tra.
Marc Gravell

3
@ Joan.bdm không có nơi nào đủ gần để tôi nhận xét về điều đó
Marc Gravell

401

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 tác vụ (TAP) cùng với async - đang chờ từ khóa trong tất cả các lĩnh vực (bao gồm GUI):

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

thay vì Mô hình lập trình không đồng bộ (APM)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 sự phát triển mới là:

  1. Triển khai không đồng bộ của 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 luồng thứ hai thông báo luồng UI:

    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à chủ đề rõ ràng.
  2. Nhiệm vụ thay vì chủ đề .
  3. từ khóa async , cho phép sử dụng chờ đợi , từ đó ngăn chặn trình xử lý sự kiện đạt đến trạng thái hoàn thành cho đến khi tác vụ hoàn thành và trong khi đó không chặn luồng UI.
  4. Lớp tiến trình (xem Giao diện IProward ) hỗ trợ nguyên tắc thiết kế Tách biệt mối quan tâm (SoC) và không yêu cầu bộ điều phối rõ ràng và gọi. Nó sử dụng SyncizationContext hiện tại từ nơi tạo của nó (ở đây là luồng UI).
  5. TaskCreationOptions.LongRucky gợi ý không xếp hàng tác vụ vào ThreadPool .

Để biết thêm một ví dụ dài dòng, hãy xem: Tương lai của C #: Những điều tốt đẹp sẽ đến với những người 'chờ đợi' của Joseph Albahari .

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

Xử lý ngoại lệ

Đoạn mã dưới đây là một ví dụ về cách xử lý các trường hợp ngoại lệ và chuyển đổi thuộc Enabledtính của nút để ngăn chặn nhiều nhấp chuột trong khi thực hiện 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...");
    }
}

2
Nếu SecondThreadConcern.LongWork()ném một ngoại lệ, nó có thể bị bắt bởi luồng UI không? Đây là một bài viết tuyệt vời, btw.
kdbanman

2
Tôi đã thêm một phần bổ sung vào câu trả lời để đáp ứng yêu cầu của bạn. Trân trọng.
Ryszard Dżegan

3
Lớp ExceptionDispatchInfo chịu trách nhiệm cho phép lạ đó là lấy lại ngoại lệ nền trên luồng UI trong mẫu async-await.
Ryszard Dżegan

1
Có phải chỉ tôi nghĩ rằng cách làm này là dài dòng hơn là chỉ gọi Invoke / Begin?!
MeTitus

2
Task.Delay(500).Wait()? Điểm tạo ra một Nhiệm vụ để chỉ chặn luồng hiện tại là gì? Bạn không bao giờ nên chặn một chủ đề pool pool!
Yarik

236

Biến thể của giải pháp đơn giản nhất của Marc Gravell cho .NET 4:

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

Hoặc sử dụng đại biểu hành động thay thế:

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

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


1
'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

Điều gì về phương pháp mở rộng như @styxriver stackoverflow.com/a35388137/206730 ?
Kiquenet

tuyên bố 'Hành động y;' bên trong lớp hoặc phương thức 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 = "văn bản mới");'
Antonio Leite

4
@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 22/03/18

1
Tôi đã theo dõi 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 phải this.refresh()vô hiệu hóa và sơn lại GUI .. nếu nó hữu ích ..
Rakibul Haq

137

Cháy và quên phương thức 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");

5
Điểm của việc sử dụng @this là gì? Sẽ không "kiểm soát" tương đương? Có bất kỳ lợi ích cho @this?
argyle

14
@jeromeyer - Đơn @thisgiản chỉ là tên biến, trong trường hợp này là tham chiếu đến điều khiển hiện tại gọi phần mở rộng. Bạn có thể đổi tên nó thành nguồn, hoặc bất cứ thứ gì nổi lên thuyền của bạn. Tôi sử dụng @this, bởi vì nó đề cập đến 'Điều khiển này' đang gọi phần mở rộng và ít nhất là (trong đầu tôi, ít nhất) với việc sử dụng từ khóa 'this' trong mã thông thường (không phải tiện ích mở rộng).
StyxRiver

1
Điều này là 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á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);});
Tự động

1
Tôi thực sự thích giải pháp này. Tiểu nit: Tôi sẽ đặt tên cho phương pháp này OnUIThreadhơn là UIThread.
ToolmakerSteve

2
Đó là lý do tại sao tôi đặt tên cho phần mở rộng này RunOnUiThread. Nhưng đó chỉ là sở thích cá nhân.
Chương trình

66

Đâ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;
        }
    }
}

Chủ đề công nhân của bạn có một sự kiện. Giao diện người dùng của bạn bắt đầu một luồng khác để thực hiện công việc và kết nối sự kiện worker đó để bạn có thể hiển thị trạng thái của luồng worker.

Sau đó, trong giao diện người dùng, bạn cần phải xử lý các luồng để thay đổi điều khiển thực tế ... như nhãn hoặc thanh tiến trình.


62

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
}

cũng được thực hiện cho đơ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 hơn như ý nghĩa của nó! để gọi 1 dòng trên luồng chính, chúng ta nên viết vài hàm!
MBH

1
@MBH Đồng ý. BTW, bạn có nhận thấy stackoverflow.com/a35388137/199364 trả lời ở trên, định nghĩa một phương thức mở rộng khô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 thêm rằng Microsoft đã không làm điều đó cho chúng tôi :)
ToolmakerSteve

@ToolmakerSteve Đó chính xác là ý nghĩa của nó! bạn nói đúng, chúng tôi có thể tìm ra cách, nhưng ý tôi là từ quan điểm DRY (đừng lặp lại chính mình), vấn đề có giải pháp chung, có thể được họ giải quyết bằng nỗ lực tối thiểu của Microsoft, điều này sẽ tiết kiệm rất nhiều thời gian cho lập trình viên :)
MBH

47

Mã luồng thường có lỗi và luôn khó kiểm tra. Bạn không cần phải 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 lớp BackgroundWorker để chạy tác vụ và phương thức ReportProTHER của nó để cập nhật giao diện người dùng. Thông thường, bạn chỉ báo cáo tỷ lệ phần trăm hoàn tất, 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. Đâ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();
    }

Điều đó tốt nếu bạ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ể xác định một lớp để thể hiện trạng thái UI và chuyển nó sang phương thức ReportProTHER.

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


2
Khi kết thúc xử lý, cũng có thể cập nhật giao diện người dùng thông qua backgroundWorker1_RunWorkerCompleted.
DavidRR

41

Phần lớn các câu trả lời sử dụng Control.Invokelà một điều kiện cuộc đua đang chờ để xảy ra . Ví dụ, hãy 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 khi this.Invokeđược gọi (hãy nhớ, thisFormđối tượng), một ObjectDisposedExceptionkhả năng sẽ bị sa thải.

Giải pháp là sử dụng SynchronizationContext, cụ thể SynchronizationContext.Currentnhư hamilton.danielb gợi ý (các câu trả lời khác dựa trên SynchronizationContextviệc triển khai cụ thể là 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.Postthay vì SynchronizationContext.Sendmặc dù (vì thông thường không cần phải có luồng công nhân chờ):

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 không đồng bộ. Xem câu trả lời của n-san cho cách tiếp cận 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>(về cơ bản nắm bắt được SynchronizationContext.Currentkhi tạo ra nó) như được trình bày bởi Ryszard Dżegan cho các trường hợp hoạt động lâu dài cần chạy mã UI trong khi vẫn hoạt động.


37

Bạn sẽ phải đảm bảo rằng bản cập nhật xảy ra trên đúng luồng; giao diện người dùng.

Để thực hiện việc 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 nâng cao sự kiện của bạn như thế này:

(Mã được gõ ở đây ra khỏi đầu tôi, vì vậy tôi đã không kiểm tra đúng cú pháp, v.v., 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 ISynchronizeInvokegiao diện.

Để chắc chắn rằng mã trên làm việc với Windows Forms và WPF, và tất cả các nền tảng khác, bạn có thể có một cái nhìn tại AsyncOperation, AsyncOperationManagerSynchronizationContextcá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 chỉ bằng cách 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 hóa vấn đề này cho bạn.


Thật vậy, nhưng tôi không muốn 'làm lộn xộn' mã GUI của mình với vấn đề này. GUI của tôi không nên quan tâm liệu nó có cần gọi hay không. Nói cách khác: tôi không nghĩ rằng đó là khả năng đáp ứng của GUI để thực hiện bối cảnh-swithc.
Frederik Gheysels

1
Chia nhỏ đại biểu, v.v ... có vẻ quá mức - tại sao không chỉ là: SyncizationContext.Cản.Send (ủy nhiệm {MyEvent (...);}, null);
Marc Gravell

Bạn có luôn có quyền truy cập vào SyncizationContext không? Ngay cả khi lớp học của bạn là trong một lớp lib?
Frederik Gheysels

29

Bạn sẽ cần gọi phương thức trên luồng GUI. Bạn có thể làm điều đó bằng cách gọi Control.Invoke.

Ví dụ:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}

1
Dòng invoke cho tôi một lỗi biên dịch. Phương thức nạp chồng tốt nhất phù hợp với 'System.Windows.Forms.Control.Invoke (System.Delegate, object [])' có một số đối số không hợp lệ
CruelIO

28

Vì sự tầm thường của kịch bản, tôi thực sự sẽ có cuộc thăm dò chủ đề UI cho trạng thái. Tôi nghĩ rằng bạn sẽ thấy rằng nó có thể khá thanh lịch.

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

Cách tiếp cận tránh được thao tác sắp xếp theo yêu cầu khi sử dụng ISynchronizeInvoke.InvokeISynchronizeInvoke.BeginInvokephương thức. Không có gì sai khi sử dụng kỹ thuật đầm lầy, nhưng có một vài lưu ý bạn cần lưu ý.

  • Hãy chắc chắn rằng bạn không gọi BeginInvokequá thường xuyên hoặc nó có thể tràn qua máy bơm tin nhắn.
  • Gọi Invoketrên luồng công nhân là một cuộc gọi chặn. Nó sẽ tạm thời dừng công việc đang được thực hiện trong chủ đề đó.

Chiến lược tôi đề xuất trong câu trả lời này đảo ngược vai trò giao tiếp của các chủ đề. Thay vì luồng công nhân đẩy dữ liệu, cuộc thăm dò chủ đề UI cho nó. Đây là một mô hình phổ biến được sử dụng trong nhiều kịch bản. Vì tất cả những gì bạn muốn làm là hiển thị thông tin tiến trình từ luồng công nhân, tôi nghĩ bạn sẽ thấy rằng giải pháp này là một giải pháp thay thế tuyệt vời cho giải pháp đầm lầy. Nó có những ưu điểm sau.

  • Các luồng UI và worker vẫn được ghép lỏng lẻo trái ngược với Control.Invokehoặc Control.BeginInvokecách tiếp cận kết hợp chặt chẽ chúng.
  • Chuỗi UI sẽ không cản trở tiến trình của luồng worker.
  • Chủ đề công nhân không thể chi phối thời gian chủ đề UI chi tiêu cập nhật.
  • Các khoảng thời gian mà các luồng UI và worker thực hiện các hoạt động có thể vẫn độc lập.
  • Luồng công nhân không thể vượt qua bơm thông báo của giao diện người dùng.
  • Chủ đề UI được ra lệnh khi nào và tần suất UI được cập nhật.

3
Ý tưởng tốt. Điều duy nhất bạn không đề cập đến là cách bạn vứt bỏ bộ hẹn giờ đúng cách sau khi WorkerThread kết thúc. Lưu ý điều này có thể gây rắc rối khi ứng dụng kết thúc (tức là người dùng đóng ứng dụng). Bạn có một ý tưởng làm thế nào để giải quyết điều này?
Matt

@Matt Thay vì sử dụng trình xử lý ẩn danh cho Elapsedsự kiện, bạn sử dụng phương thức thành viên để bạn có thể xóa bộ đếm thời gian khi biểu mẫu được xử lý ...
Phil1970

@ Phil1970 - Điểm tốt. Bạn có nghĩa là thích System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };và chỉ định nó thông qua m_Timer.Elapsed += handler;, sau này trong bối cảnh xử lý làm m_Timer.Elapsed -= handler;tôi phải không? Và cho việc xử lý / đóng theo lời khuyên như được thảo luận ở đây .
Matt

27

Không có công cụ Gọi nào trong các câu trả lời trước là cần thiết.

Bạn cần xem WindowsFormsSyn syncizationContext:

// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();

...

// In some non-UI Thread

// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);

...

void UpdateGUI(object userData)
{
    // Update your GUI controls here
}

4
Bạn nghĩ gì về phương pháp Post sử dụng dưới mui xe? :)
hoài nghi

23

Giải pháp này tương tự như giải pháp trên sử dụng .NET Framework 3.0, nhưng nó đã giải quyết được vấn đề hỗ trợ an toàn thời gian biên dịch .

public  static class ControlExtension
{
    delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);

    public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
    {
        if (source.InvokeRequired)
        {
            var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
            source.Invoke(del, new object[]{ source, selector, value});
        }
        else
        {
            var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
            propInfo.SetValue(source, value, null);
        }
    }
}

Để sử dụng:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

Trình biên dịch sẽ thất bại nếu người dùng chuyển loại dữ liệu sai.

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");

23

Cứu cánh! Sau khi tìm kiếm câu hỏi này, tôi thấy câu trả lời của FrankGOregon Ghost là hữu ích nhất đối với tôi. Bây giờ, tôi viết mã trong Visual Basic và chạy đoạn mã này thông qua trình chuyển đổi; Vì vậy, tôi không chắc chắn làm thế nào nó bật ra.

Tôi có một dạng hộp thoại được gọi là form_Diagnostics,có hộp richtext, được gọi là hộp updateDiagWindow,hiển thị nhật ký. Tôi cần để có thể cập nhật văn bản của nó từ tất cả các chủ đề. Các dòng phụ cho phép cửa sổ tự động cuộn đến các dòng mới nhất.

Và vì vậy, bây giờ tôi có thể cập nhật màn hình với một dòng, từ bất kỳ đâu trong toàn bộ chương trình theo cách mà bạn nghĩ nó sẽ hoạt động mà không cần phân luồng:

  form_Diagnostics.updateDiagWindow(whatmessage);

Mã chính (đặt mã này bên trong mã lớp của biểu mẫu của bạn):

#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion

21

Đối với nhiều mục đích, nó đơn giản như thế này:

public delegate void serviceGUIDelegate();
private void updateGUI()
{
  this.Invoke(new serviceGUIDelegate(serviceGUI));
}

"serviceGUI ()" là một phương thức mức GUI trong biểu mẫu (cái này) có thể thay đổi nhiều điều khiển như bạn muốn. Gọi "updateGUI ()" từ chuỗi khác. Các tham số có thể được thêm vào để vượt qua các giá trị hoặc (có thể nhanh hơn) sử dụng các biến phạm vi lớp với các khóa trên chúng theo yêu cầu nếu có bất kỳ khả năng xảy ra xung đột giữa các luồng truy cập vào chúng có thể gây mất ổn định. Sử dụng BeginInvoke thay vì Gọi nếu luồng không phải GUI là thời gian quan trọng (luôn ghi nhớ cảnh báo của Brian Gideon).


21

Điều này trong biến thể C # 3.0 của tôi về giải pháp của Ian Kemp:

public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control
{
    var memberExpression = property.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    if (control.InvokeRequired)
        control.Invoke(
            (Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread,
            new object[] { control, property, value }
        );
    else
        propertyInfo.SetValue(control, value, null);
}

Bạn gọi nó như thế này:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. Nó thêm kiểm tra null vào kết quả của "as MemberExpression".
  2. Nó cải thiện loại an toàn tĩnh.

Nếu không, bản gốc là một giải pháp rất tốt đẹp.


21
Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

Lưu ý rằng BeginInvoke()được ưu tiên hơn Invoke()vì nó ít có khả năng gây ra bế tắc (tuy nhiên, đây không phải là vấn đề ở đây khi chỉ gán văn bản cho nhãn):

Khi sử dụng Invoke()bạn đang chờ phương thức trở lại. Bây giờ, có thể là bạn làm một cái gì đó trong mã được gọi sẽ cần đợi chuỗi, điều này có thể không rõ ràng ngay lập tức nếu nó bị chôn vùi trong một số chức năng mà bạn đang gọi, chính nó có thể xảy ra gián tiếp thông qua các trình xử lý sự kiện. Vì vậy, bạn sẽ chờ đợi chủ đề, chủ đề sẽ chờ đợi bạn và bạn đang bế tắc.

Điều này thực sự khiến một số phần mềm được phát hành của chúng tôi bị treo. Nó đã đủ dễ dàng để sửa chữa bằng cách thay thế Invoke()bằng BeginInvoke(). Trừ khi bạn có nhu cầu hoạt động đồng bộ, có thể là trường hợp nếu bạn cần một giá trị trả về, hãy sử dụng BeginInvoke().


20

Khi tôi gặp phải vấn đề tương tự, tôi đã tìm kiếm sự giúp đỡ từ Google, nhưng thay vì đưa cho tôi một giải pháp đơn giản, nó làm tôi bối rối hơn bằng cách đưa ra các ví dụ về MethodInvokervà blah blah blah. Vì vậy, tôi quyết định tự mình giải quyết nó. Đây là giải pháp của tôi:

Làm một đại biểu như thế này:

Public delegate void LabelDelegate(string s);

void Updatelabel(string text)
{
   if (label.InvokeRequired)
   {
       LabelDelegate LDEL = new LabelDelegate(Updatelabel);
       label.Invoke(LDEL, text);
   }
   else
       label.Text = text
}

Bạn có thể gọi hàm này trong một chủ đề mới như thế này

Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();

Đừng nhầm lẫn với Thread(() => .....). Tôi sử dụng một hàm ẩn danh hoặc biểu thức lambda khi tôi làm việc trên một chủ đề. Để giảm các dòng mã, bạn cũng có thể sử dụng ThreadStart(..)phương thức mà tôi không phải giải thích ở đây.


17

Đơn giản chỉ cần sử dụng một cái gì đó như thế này:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });

Nếu bạn có e.ProgressPercentage, bạn đã không có trong giao diện người dùng từ phương thức bạn đang gọi này chưa?
LarsTech

Sự kiện ProgressChanged chạy trên luồng UI. Đó là một trong những tiện ích của việc sử dụng BackgroundWorker. Sự kiện Hoàn thành cũng chạy trên gui. Điều duy nhất chạy trong luồng không phải UI là phương thức DoWork.
LarsTech

15

Bạn có thể sử dụng đại biểu đã tồn tại Action:

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}

14

Phiên bản của tôi là chèn một dòng "thần chú" đệ quy:

Không có đối số:

    void Aaaaaaa()
    {
        if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra

        // Your code!
    }

Đối với một hàm có đối số:

    void Bbb(int x, string text)
    {
        if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
        // Your code!
    }

Đó là CNTT .


Một số đối số : Thông thường việc đọc mã {} sau một if ()câu lệnh trong một dòng là không tốt. Nhưng trong trường hợp này, đó là "câu thần chú" thường xuyên giống nhau. Nó không phá vỡ khả năng đọc mã nếu phương pháp này phù hợp với dự án. Và nó lưu mã của bạn khỏi xả rác (một dòng mã thay vì năm).

Như bạn thấy if(InvokeRequired) {something long}bạn chỉ biết "chức năng này an toàn để gọi từ một luồng khác".


13

Cố gắng làm mới nhãn bằng cách này

public static class ExtensionMethods
{
    private static Action EmptyDelegate = delegate() { };

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}

Có phải cho Windows Forms ?
Kiquenet

13

Tạo một biến lớp:

SynchronizationContext _context;

Đặt nó trong hàm tạo tạo UI của bạn:

var _context = SynchronizationContext.Current;

Khi bạn muốn cập nhật nhãn:

_context.Send(status =>{
    // UPDATE LABEL
}, null);

12

Bạn phải sử dụng invoke và ủy nhiệm

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });

12

Hầu hết các câu trả lời khác là một chút phức tạp đối với tôi về câu hỏi này (tôi mới biết về C #), vì vậy tôi đang viết của tôi:

Tôi có WPF ứng dụng và đã định nghĩa một nhân viên như sau:

Vấn đề:

BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
    // This is my DoWork function.
    // It is given as an anonymous function, instead of a separate DoWork function

    // I need to update a message to textbox (txtLog) from this thread function

    // Want to write below line, to update UI
    txt.Text = "my message"

    // But it fails with:
    //  'System.InvalidOperationException':
    //  "The calling thread cannot access this object because a different thread owns it"
}

Giải pháp:

workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
    // The below single line works
    txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}

Tôi vẫn chưa tìm hiểu ý nghĩa của dòng trên, nhưng nó hoạt động.

Dành cho WinForms :

Giải pháp:

txtLog.Invoke((MethodInvoker)delegate
{
    txtLog.Text = "my message";
});

Câu hỏi là về Winforms, không phải WPF.
Marc L.

Cảm ơn. Đã thêm giải pháp WinForms ở trên.
Manohar Reddy Poreddy

... đó chỉ là một bản sao của nhiều câu trả lời khác cho cùng một câu hỏi, nhưng không sao. Tại sao không phải là một phần của giải pháp và chỉ cần xóa câu trả lời của bạn?
Marc L.

hmm, đúng là bạn, nếu chỉ, bạn chú ý đọc câu trả lời của tôi, phần đầu (lý do tại sao tôi viết câu trả lời), và hy vọng với một chút chú ý bạn sẽ thấy có ai đó có cùng một vấn đề & được nêu lên hôm nay cho câu trả lời đơn giản của tôi và thậm chí còn chú ý hơn nếu bạn có thể thấy trước câu chuyện thực sự về lý do tại sao tất cả những điều này xảy ra, google sẽ gửi tôi đến đây ngay cả khi tôi tìm kiếm wpf. Chắc chắn vì bạn đã bỏ lỡ 3 lý do ít nhiều rõ ràng này, tôi có thể hiểu lý do tại sao bạn sẽ không xóa downvote của mình. Thay vì làm sạch cái ổn, hãy tạo một cái gì đó mới khó hơn nhiều.
Manohar Reddy Poreddy

8

Cách dễ nhất tôi nghĩ:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }

8

Ví dụ: truy cập một điều khiển khác ngoài luồng hiện tại:

Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
    lblThreshold.Text = Speed_Threshold.ToString();
}));

lblThresholdmột Nhãn và Speed_Thresholdlà một biến toàn cục.


8

Khi bạn đang ở trong giao diện người dùng, bạn có thể yêu cầu nó cho trình lập lịch tác vụ ngữ cảnh đồng bộ hóa của nó. Nó sẽ cung cấp cho bạn một TaskScheduler lên lịch mọi thứ trên luồng UI.

Sau đó, bạn có thể xâu chuỗi các tác vụ của mình để khi kết quả sẵn sàng thì một tác vụ khác (được lên lịch trên luồng UI) chọn nó và gán nó vào nhãn.

public partial class MyForm : Form
{
  private readonly TaskScheduler _uiTaskScheduler;
  public MyForm()
  {
    InitializeComponent();
    _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  }

  private void buttonRunAsyncOperation_Click(object sender, EventArgs e)
  {
    RunAsyncOperation();
  }

  private void RunAsyncOperation()
  {
    var task = new Task<string>(LengthyComputation);
    task.ContinueWith(antecedent =>
                         UpdateResultLabel(antecedent.Result), _uiTaskScheduler);
    task.Start();
  }

  private string LengthyComputation()
  {
    Thread.Sleep(3000);
    return "47";
  }

  private void UpdateResultLabel(string text)
  {
    labelResult.Text = text;
  }
}

Điều này hoạt động cho các nhiệm vụ (không phải chủ đề) là cách viết mã đồng thời ưa thích bây giờ .


1
Gọi điện Task.Startthường không phải là một blog
2012

8

Tôi chỉ đọc câu trả lời và đây dường như là một chủ đề rất nóng. Tôi hiện đang sử dụng .NET 3.5 SP1 và Windows Forms.

Công thức nổi tiếng được mô tả rất nhiều trong các câu trả lời trước đó sử dụng InvokeRequired bao gồm hầu hết các trường hợp, nhưng không phải toàn bộ nhóm.

Điều gì xảy ra nếu Xử lý chưa được tạo?

Các InvokeRequired tài sản, như mô tả ở đây (Control.InvokeRequired tài sản tham chiếu đến MSDN) trả về true nếu cuộc gọi được thực hiện từ một sợi đó không phải là chủ đề giao diện, sai sự thật hoặc nếu cuộc gọi được thực hiện từ sợi GUI, hoặc nếu Xử lý là chưa được tạo

Bạn có thể gặp một ngoại lệ nếu bạn muốn có một hình thức phương thức được hiển thị và cập nhật bởi một chủ đề khác. Vì bạn muốn biểu mẫu đó được hiển thị một cách vừa phải, bạn có thể thực hiện như sau:

private MyForm _gui;

public void StartToDoThings()
{
    _gui = new MyForm();
    Thread thread = new Thread(SomeDelegate);
    thread.Start();
    _gui.ShowDialog();
}

Và đại biểu có thể cập nhật Nhãn trên GUI:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.InvokeRequired)
        _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
    else
        _gui.Label1.Text = "Done!";
}

Điều này có thể gây ra một UnlimitedOperationException nếu các thao tác trước khi cập nhật nhãn "mất ít thời gian hơn" (đọc và giải thích nó như một sự đơn giản hóa) so với thời gian để chủ đề GUI tạo ra Xử lý của biểu mẫu . Điều này xảy ra trong ShowDialog () phương thức .

Bạn cũng nên kiểm tra Xử lý như thế này:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.IsHandleCreated)  //  <---- ADDED
        if(_gui.InvokeRequired)
            _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
        else
            _gui.Label1.Text = "Done!";
}

Bạn có thể xử lý các hoạt động để thực hiện nếu Xử lý chưa được tạo: Bạn có thể bỏ qua bản cập nhật GUI (như được hiển thị trong đoạn mã trên) hoặc bạn có thể đợi (rủi ro hơn). Điều này sẽ trả lời câu hỏi.

Công cụ tùy chọn: Cá nhân tôi đã đưa ra mã hóa như sau:

public class ThreadSafeGuiCommand
{
  private const int SLEEPING_STEP = 100;
  private readonly int _totalTimeout;
  private int _timeout;

  public ThreadSafeGuiCommand(int totalTimeout)
  {
    _totalTimeout = totalTimeout;
  }

  public void Execute(Form form, Action guiCommand)
  {
    _timeout = _totalTimeout;
    while (!form.IsHandleCreated)
    {
      if (_timeout <= 0) return;

      Thread.Sleep(SLEEPING_STEP);
      _timeout -= SLEEPING_STEP;
    }

    if (form.InvokeRequired)
      form.Invoke(guiCommand);
    else
      guiCommand();
  }
}

Tôi cung cấp các biểu mẫu của mình được cập nhật bởi một luồng khác với phiên bản của ThreadSafeGuiCommand này và tôi xác định các phương thức cập nhật GUI (trong Biểu mẫu của tôi) như sau:

public void SetLabeTextTo(string value)
{
  _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });
}

Theo cách này, tôi khá chắc chắn rằng tôi sẽ cập nhật GUI của mình bất kỳ luồng nào sẽ thực hiện cuộc gọi, tùy ý chờ một khoảng thời gian được xác định rõ (thời gian chờ).


1
Đến đây để tìm cái này, vì tôi cũng kiểm tra IsHandleCreated. Một thuộc tính khác để kiểm tra là IsDisposed. Nếu biểu mẫu của bạn bị loại bỏ, bạn không thể gọi Invoke () trên đó. Nếu người dùng đã đóng biểu mẫu trước khi chủ đề nền của bạn có thể hoàn tất, bạn không muốn nó cố gọi lại UI khi biểu mẫu bị loại bỏ.
Jon

Tôi sẽ nói rằng đó là một ý tưởng tồi khi bắt đầu với ... Thông thường, bạn sẽ hiển thị biểu mẫu con ngay lập tức và có một thanh tiến trình hoặc một số phản hồi khác trong khi xử lý nền. Hoặc bạn sẽ thực hiện tất cả xử lý trước và sau đó chuyển kết quả sang biểu mẫu mới khi tạo. Làm cả hai cùng một lúc thường sẽ có lợi ích cận biên nhưng mã ít bảo trì hơn nhiều.
Phil1970

Kịch bản được mô tả có tính đến một hình thức phương thức được sử dụng làm chế độ xem tiến trình của công việc xử lý nền. Bởi vì nó phải là phương thức, nó phải được hiển thị bằng cách gọi phương thức Form.ShowDialog () . Bằng cách này, bạn ngăn mã của mình theo lệnh gọi được thực thi cho đến khi biểu mẫu được đóng lại. Vì vậy, trừ khi bạn có thể bắt đầu luồng nền khác với ví dụ đã cho (và tất nhiên, bạn có thể) biểu mẫu này phải được hiển thị một cách vừa phải sau khi luồng nền đã bắt đầu. Trong trường hợp này, bạn cần kiểm tra Xử lý được tạo. Nếu bạn không cần một hình thức phương thức, thì đó là một câu chuyện khác.
Sume
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.