如何同步这两个线程?

时间:2010-12-20 02:12:30

标签: c# multithreading loops synchronization

这是班级:

public class Ticker
{
    public event EventHandler Tick;
    public EventArgs e = null;
    public void TickIt()
    {
        while (true)
        {
            System.Threading.Thread.Sleep(300);
            if (Tick != null)
            {
                Tick(this, e);
            }
        }
    }

我在Windows窗体中运行两个线程:

public partial class Form1 : Form
{
    Ticker ticker1 = new Ticker();
    Ticker ticker2 = new Ticker();
    Thread t;
    Thread t1;

    public Form1()
    {
        InitializeComponent();
        ticker1.Tick += ticker1_Tick;
        ticker2.Tick += ticker2_Tick;

        t = new Thread(new ThreadStart(ticker1.TickIt));
        t1 = new Thread(new ThreadStart(ticker2.TickIt)));
        t.Start();
        t1.Start();

    }
    public void ticker1_Tick(object sender, EventArgs e)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke((MethodInvoker)delegate
            {
                ticker1_Tick(sender, e);
            });
            return;
        } 


        richTextBox1.Text += "t1 ";
    }
    public void ticker2_Tick(object sender, EventArgs e)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke((MethodInvoker)delegate
            {
                ticker2_Tick(sender, e);
            });
            return;
        } 


        richTextBox2.Text += "t2 ";
    }

问题是在几秒钟之后,线程t在t1之前几个滴答。

首先为什么会发生这种情况,这是没有意义的,因为每个线程应该在滴答前等待300毫秒?

其次,我如何同步这两个线程,所以它们同时勾选,一个不会超越另一个?

我不能在while循环之前放置一个锁,然后只有一个线程会运行,而另一个线程被锁定。在别处放锁并不会改变任何事情。

5 个答案:

答案 0 :(得分:2)

我认为你不能相信睡眠(300)让你的线程独立运行相同的次数......

你可以做的一件事就是有一个中央计时器/标记生成器,它在每个标记上发出同步对象的信号,并且线程函数只勾选一次,然后WaitsForObject用于从主线程生成下一个标记,实际上有一个计时器并告诉线程同步打勾。

另请注意,您订阅线程函数事件的方式,需要考虑处理函数中的竞争条件。每个方法都将在它自己的线程上运行(直到begininvoke),因此,如果您访问任何资源(类字段等),则需要同步这些方法。忘记线程发生的事情太容易了。 :(

答案 1 :(得分:2)

如果你真的需要它们完全同步并按特定顺序执行刻度,你需要某种中央计时器,正如Jaime所说。如果你需要独立的时序但是想要防止因睡眠不精确引起的漂移,或者在执行事件处理程序所需的时间之间增加延迟,那么这样的事情就可以了:

public class Ticker
{
    public event EventHandler Tick;
    public EventArgs e = null;
    public void TickIt()
    {
        const int targetSleepTime = 300;
        int nextTick = Environment.TickCount + targetSleepTime;
        while (true)
        {
            System.Threading.Thread.Sleep(Math.Max(nextTick - Environment.TickCount, 0));
            if (Tick != null)
            {
                Tick(this, e);
            }
            nextTick += targetSleepTime;
        }
    }
}

请记住,Environment.TickCount在到达Int32.MaxValue时可以回绕到Int32.MinValue。你需要额外的代码来处理它,或者可能是基于DateTime.UtcNow的时间(比DateTime.Now更少的开销)。

答案 2 :(得分:2)

如何使用 AutoResetEvent

class Program
{
    static readonly AutoResetEvent thread1Step = new AutoResetEvent(false);
    static readonly AutoResetEvent thread2Step = new AutoResetEvent(false);

    static void Main(string[] args)
    {
        new Thread(new ThreadStart(Thread1Main)).Start();
        new Thread(new ThreadStart(Thread2Main)).Start();
    }

    private static void Thread1Main()
    {
        for (int i = 0; i < int.MaxValue; i++)
        {
            Console.WriteLine("thread1 i=" + i);
            thread1Step.Set();
            thread2Step.WaitOne();
        }
    }

    private static void Thread2Main()
    {
        for (int i = 0; i < int.MaxValue; i++)
        {
            Console.WriteLine("thread2 i=" + i);
            thread2Step.Set();
            thread1Step.WaitOne();
        }
    }
}

答案 3 :(得分:0)

如果您使用的是.NET 4.0,那么您可以使用屏障,但是您必须将它放在Ticker类中,否则您将阻止您的UI线程。

http://msdn.microsoft.com/en-us/library/system.threading.barrier.aspx

答案 4 :(得分:0)

在你的Ticker课程中,增加你的轮询频率并检查系统计时器,直到你达到你想要的间隔。如果您可以使用毫秒精度,则可以使用TickCountTicks,如果您的系统支持,则可以使用StopWatch以获得更高的精度。

为了使它们保持同步,它们需要一个开始时间的公共参考。您可以将此作为特定的未来标记传递以开始同步或使用类似Tick模数100的内容。或者轮询表示何时开始的共享静态标记。

您不能具有绝对精度,因此请从一开始就定义您可以使用的精度范围,例如Ticker线程之间的正负5ms。

有一件事有用,就是启动一个共享的静态StopWatch实例,并在所有日志记录中回显它所用的时间,以帮助您描述应用中的任何延迟。