C# 跨线程交互

发布于 2024-12-27 17:03:38 字数 2465 浏览 1 评论 0原文

有人可以帮助我如何在我的类中设置 Thread.join() 方法,或者是否有一种巧妙的方法来处理 SynchronizationContext 类和 thread.join 方法。基本上,我试图从不同的线程(不是 UI 线程)每 2 秒更新一次 datagridview (dgv) 单元格和进度条 (pb)。当一个线程完成这项工作时,该功能可以正常工作;但是,我想设置 2 个线程,以便第一个线程(线程 1)将更新控件(在我的例子中,它将更新 datagridview 并显示 10 行,进度条将更新到 50%)。一旦线程 1 完成其工作,线程 2 就应该启动并更新控件(在我的例子中,它将更新 datagridview 并显示另外 10 行,进度条将更新到 100%)。请参阅下面的代码。

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

namespace DelegatesAndCallback
{
public partial class Form1 : Form
{
    private Thread newThread1;
    private Thread newThread2;

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {

        int id = Thread.CurrentThread.ManagedThreadId;
        Trace.WriteLine("Button thread "+id);

        SynchronizationContext uiContext = SynchronizationContext.Current;

        // thread #1
        startThread1(uiContext);

        // thread #2
        startThread2(uiContext);
    }

    public void startThread1(SynchronizationContext sc)
    {
        // thread #1
        newThread1 = new Thread(Process1) { Name = "Thread 1" };
        newThread1.Start(sc);
        //newThread1.Join();
    }

    public void startThread2(SynchronizationContext sc)
    {
        // thread #2
        newThread2 = new Thread(Process2) { Name = "Thread 2" };
        newThread2.Start(sc);
        //newThread2.Join();
    }

    public  void updateProgressBarValue(object state)
    {
        double val = Convert.ToDouble(state)/19.0;
        pb.Value = (int)(100*val);
    }

    public  void updateDataGridViewValue(object state)
    {
        dgv.Rows.Add((int) state, (int) state);
    }

    public void Process1(object state)
    {
        SynchronizationContext uiContext = state as SynchronizationContext;

        for (int i = 0; i < 10; i++)
        {
            uiContext.Send(updateDataGridViewValue, i);
            uiContext.Send(updateProgressBarValue, i);
            Thread.Sleep(2000);
        }
    }

    public void Process2(object state)
    {
        SynchronizationContext uiContext = state as SynchronizationContext;

        for (int i = 10; i < 20; i++)
        {
            if (uiContext != null) uiContext.Send(updateProgressBarValue, i);
            if (uiContext != null) uiContext.Send(updateDataGridViewValue, i);
            Thread.Sleep(2000);
        }
    }
}
}

can someone help me how to set Thread.join() method within my class or if there is a neat way how to deal with SynchronizationContext class and thread.join method. basically, im trying to update a datagridview (dgv) cell and progress bar (pb) every 2 seconds from a different thread (not UI thread). the functionality works fine when one thread does the job; however, i would like to set 2 threads so that the first thread (Thread 1) will update the controls (in my case, it will update the datagridview and display 10 rows and the progress bar will be update to 50%). as soon as thread 1 has done its job, Thread 2 should start and update the controls (in my case, it will update the datagridview and display 10 more rows and the progress bar will be update to 100%). Please see code below.

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

namespace DelegatesAndCallback
{
public partial class Form1 : Form
{
    private Thread newThread1;
    private Thread newThread2;

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {

        int id = Thread.CurrentThread.ManagedThreadId;
        Trace.WriteLine("Button thread "+id);

        SynchronizationContext uiContext = SynchronizationContext.Current;

        // thread #1
        startThread1(uiContext);

        // thread #2
        startThread2(uiContext);
    }

    public void startThread1(SynchronizationContext sc)
    {
        // thread #1
        newThread1 = new Thread(Process1) { Name = "Thread 1" };
        newThread1.Start(sc);
        //newThread1.Join();
    }

    public void startThread2(SynchronizationContext sc)
    {
        // thread #2
        newThread2 = new Thread(Process2) { Name = "Thread 2" };
        newThread2.Start(sc);
        //newThread2.Join();
    }

    public  void updateProgressBarValue(object state)
    {
        double val = Convert.ToDouble(state)/19.0;
        pb.Value = (int)(100*val);
    }

    public  void updateDataGridViewValue(object state)
    {
        dgv.Rows.Add((int) state, (int) state);
    }

    public void Process1(object state)
    {
        SynchronizationContext uiContext = state as SynchronizationContext;

        for (int i = 0; i < 10; i++)
        {
            uiContext.Send(updateDataGridViewValue, i);
            uiContext.Send(updateProgressBarValue, i);
            Thread.Sleep(2000);
        }
    }

    public void Process2(object state)
    {
        SynchronizationContext uiContext = state as SynchronizationContext;

        for (int i = 10; i < 20; i++)
        {
            if (uiContext != null) uiContext.Send(updateProgressBarValue, i);
            if (uiContext != null) uiContext.Send(updateDataGridViewValue, i);
            Thread.Sleep(2000);
        }
    }
}
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一梦浮鱼 2025-01-03 17:03:38

请参阅 Control.Invoke(),它专门设计用于让非UI 线程与进度条等交互。在这种情况下,使用 Invoke 将取代您的同步上下文以及对其 Send() 方法的使用。

稍微相关的说明:创建线程的更简单的方法是:

new Thread(
  () => {
   /// insert code you want executed in a separate thread here...
  }
  ).Start();

更新
如果您需要从不同的类更新进度条,我可能会这样做:

public partial class Form1 : Form
{
    private ThreadOwner _threadOwner;

    public Form1()
    {
        InitializeComponent();
        var _threadOwner = new ThreadOwner();
        _threadOwner.StartAThread(this,progressBar1.Minimum,progressBar1.Maximum);
    }

    protected override void OnClosing(CancelEventArgs e)
    {
        _threadOwner.Exit();

        base.OnClosing(e);
    }

    internal void SetProgress(int progress)
    {
        if (progressBar1.InvokeRequired)
        {
            progressBar1.Invoke(
                new Action<Form1>(
                    (sender) => { 
                        SetProgress(progress); 
                    }
                    ),new[] { this }
                    );

        }
        else
            progressBar1.Value = progress;
    }
}

ThreadOwner 类:

public class ThreadOwner
{
    private bool _done = false;

    public void StartAThread(Form1 form, int min, int max)
    {
        var progress = min;

        new Thread(() =>
            {
                while (!_done)
                {
                    form.SetProgress(progress);

                    if (progress++ > max)
                    {
                        progress = min;
                    }
                }

            }
        ).Start();
    }

    internal void Exit()
    {
        _done = true;
    }
}

要点是线程需要对表单实例的引用,该实例公开一个更新进度条的方法。然后该方法确保更新发生在正确的线程中。

See Control.Invoke(), which is specifically designed to let non-UI threads interact with things like progress bars. In this case, use of Invoke would replace your synchronization context and your use of its Send() method.

On a slightly related note: a far easier way to create a thread is:

new Thread(
  () => {
   /// insert code you want executed in a separate thread here...
  }
  ).Start();

Update
If you need to update the progress bar from a different class, I might do something like this:

public partial class Form1 : Form
{
    private ThreadOwner _threadOwner;

    public Form1()
    {
        InitializeComponent();
        var _threadOwner = new ThreadOwner();
        _threadOwner.StartAThread(this,progressBar1.Minimum,progressBar1.Maximum);
    }

    protected override void OnClosing(CancelEventArgs e)
    {
        _threadOwner.Exit();

        base.OnClosing(e);
    }

    internal void SetProgress(int progress)
    {
        if (progressBar1.InvokeRequired)
        {
            progressBar1.Invoke(
                new Action<Form1>(
                    (sender) => { 
                        SetProgress(progress); 
                    }
                    ),new[] { this }
                    );

        }
        else
            progressBar1.Value = progress;
    }
}

And the ThreadOwner class:

public class ThreadOwner
{
    private bool _done = false;

    public void StartAThread(Form1 form, int min, int max)
    {
        var progress = min;

        new Thread(() =>
            {
                while (!_done)
                {
                    form.SetProgress(progress);

                    if (progress++ > max)
                    {
                        progress = min;
                    }
                }

            }
        ).Start();
    }

    internal void Exit()
    {
        _done = true;
    }
}

The gist is that the thread needs a reference to your form instance, which exposes a method to update the progress bar. That method then makes sure that the update happens in the correct thread.

巴黎盛开的樱花 2025-01-03 17:03:38

要同步线程,您应该使用[Manual|Auto]ResetEvents。
您应该使用其他模式来编写安全的代码。
请调查我的代码:

public interface IProgress
{
    ManualResetEvent syncEvent { get; }
    void updateProgressBarValue(int state);
    void updateDataGridViewValue(int state);
}

public partial class Form1 : Form, IProgress
{
    // Sync object will be used to syncronize threads
    public ManualResetEvent syncEvent { get; private set; }

    public Form1()
    {
    }

    private void button1_Click(object sender, EventArgs e)
    {
        // Creeate sync object in unsignalled state
        syncEvent = new ManualResetEvent(false);

        // I like Async model to start background workers
        // That code will utilize threads from the thread pool
        ((Action<IProgress>)Process1).BeginInvoke(this, null, null);
        ((Action<IProgress>)Process2).BeginInvoke(this, null, null);
    }

    public void updateProgressBarValue(int state)
    {
        // InvokeRequired? -> Invoke pattern will prevent UI update from the non UI thread
        if (InvokeRequired)
        {
            // If current thread isn't UI method will invoke into UI thread itself
            Invoke((Action<int>)updateProgressBarValue, state);
            return;
        }

        double val = Convert.ToDouble(state) / 19.0;
        pb.Value = (int)(100 * val);
    }

    public void updateDataGridViewValue(int state)
    {
        if (InvokeRequired)
        {
            Invoke((Action<int>)updateDataGridViewValue, state);
            return;
        }

        dgv.Rows.Add((int)state, (int)state);
    }

    public void Process1(IProgress progress)
    {
        for (int i = 0; i < 10; i++)
        {
            // We have InvokeRequired in the methods and don't need any other code to invoke it in UI thread
            progress.updateDataGridViewValue(i);
            progress.updateProgressBarValue(i);
            Thread.Sleep(2000);
        }

        // When thread 1 complete its job we will set sync object to signalled state to wake up thread 2
        syncEvent.Set();
    }

    public void Process2(IProgress progress)
    {
        // Thread 2 will stop until sync object signalled
        syncEvent.WaitOne();

        for (int i = 10; i < 20; i++)
        {
            progress.updateProgressBarValue(i);
            progress.updateDataGridViewValue(i);
            Thread.Sleep(2000);
        }
    }
}

代码已更新以从不同的类调用 UI 更新

To syncronize threads you should use [Manual|Auto]ResetEvents.
You should use other patterns to write a safe code.
Investigate my code please:

public interface IProgress
{
    ManualResetEvent syncEvent { get; }
    void updateProgressBarValue(int state);
    void updateDataGridViewValue(int state);
}

public partial class Form1 : Form, IProgress
{
    // Sync object will be used to syncronize threads
    public ManualResetEvent syncEvent { get; private set; }

    public Form1()
    {
    }

    private void button1_Click(object sender, EventArgs e)
    {
        // Creeate sync object in unsignalled state
        syncEvent = new ManualResetEvent(false);

        // I like Async model to start background workers
        // That code will utilize threads from the thread pool
        ((Action<IProgress>)Process1).BeginInvoke(this, null, null);
        ((Action<IProgress>)Process2).BeginInvoke(this, null, null);
    }

    public void updateProgressBarValue(int state)
    {
        // InvokeRequired? -> Invoke pattern will prevent UI update from the non UI thread
        if (InvokeRequired)
        {
            // If current thread isn't UI method will invoke into UI thread itself
            Invoke((Action<int>)updateProgressBarValue, state);
            return;
        }

        double val = Convert.ToDouble(state) / 19.0;
        pb.Value = (int)(100 * val);
    }

    public void updateDataGridViewValue(int state)
    {
        if (InvokeRequired)
        {
            Invoke((Action<int>)updateDataGridViewValue, state);
            return;
        }

        dgv.Rows.Add((int)state, (int)state);
    }

    public void Process1(IProgress progress)
    {
        for (int i = 0; i < 10; i++)
        {
            // We have InvokeRequired in the methods and don't need any other code to invoke it in UI thread
            progress.updateDataGridViewValue(i);
            progress.updateProgressBarValue(i);
            Thread.Sleep(2000);
        }

        // When thread 1 complete its job we will set sync object to signalled state to wake up thread 2
        syncEvent.Set();
    }

    public void Process2(IProgress progress)
    {
        // Thread 2 will stop until sync object signalled
        syncEvent.WaitOne();

        for (int i = 10; i < 20; i++)
        {
            progress.updateProgressBarValue(i);
            progress.updateDataGridViewValue(i);
            Thread.Sleep(2000);
        }
    }
}

Code was updated to call UI update from the different classes

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文