繁忙等待线程

时间:2012-03-08 18:15:58

标签: c# .net multithreading thread-safety

基本上,我需要忙着等到网页上出现一些html。我已经创建了以下代码来忙着等我:

public void ExecuteBusyWaitThreads()
    {

        foreach (Canidate canidate in allCanidates)
        {
            Thread newThread = new Thread(delegate()
            {
                BusyWait(canidate);
            });

            newThread.Start();
        }
    }

    public bool BusyWait(Canidate canidate)
    {
        //hit that url, and wait for the claim all button to appear
        string page = null;
        while (found == false)
        {
            HttpWebRequest request = Canidate.GetHTTPRequest(canidate.URL);
            //make sure we add the authentication cookes to the request
            request = Canidate.AddCookiesToRequest(request, canidate.GetCookies());
            page = new Canidate().GetPage(request);
            if (page.ToLower().Contains("claim all"))
            {
                found = true;
                NotifyAllThreads();
            }
        }
        return true;
    }

所以,如果我有8 canidates,它会产生8个线程,每个线程都在寻找claim all出现在网页上。 found是一个全局变量。一旦其中一个线程找到claim all,他们都应该保释。

我对这种方法有几个问题。首先,这是一个很好的方法。其次,每个线程都会获得忙等待功能的“副本”。我的意思是,一个线程可以抢占另一个线程并更改该函数中的数据,或者它们每个都获得函数内声明的变量的副本。请注意,这两个函数都在同一个对象中。

4 个答案:

答案 0 :(得分:5)

在我回答你的问题之前,我必须指出你犯了closing over the loop variable的恶劣行为。

  

首先,它是一个很好的方法。

不,不是真的。任意创建线程通常不是一个好主意。最好使用线程池技术。这可以使用ThreadPool.QueueUserWorkItemTask类来完成。

  

其次,每个线程都会获得忙等待功能的“副本”。   我的意思是,一个线程可以抢占另一个线程并更改数据   该函数,或者它们每个都获得声明的变量的副本   在函数内部。

BusyWait的每个正在运行的实例都将获得所有局部变量(即pagerequest)的副本。由于found在非本地范围内被声明(可能是无论如何),因此它将在BusyWait的所有正在运行的实例之间共享。因此,您对found的当前读取和写入不是线程安全的,因为没有适当的同步机制。

答案 1 :(得分:2)

  

其次,每个线程都会获得忙等待功能的“副本”

每个线程都会使用自己的堆栈空间执行该函数,这意味着函数内部的任何变量都属于它所运行的线程。如果你有一个全局变量,比如在函数内部改变你的found变量,你需要设置一个同步机制,这样就不会同时从多个线程中访问它,因为这会导致很难找到你不想想象的错误和许多恐怖事件!

答案 2 :(得分:2)

所有线程都获得自己的局部变量副本(在这种情况下只有string page)。

您的共享found变量应声明为volatile

这是一种罕见的情况,呼叫Thread.Sleep()可能会带来一些好处。在对同一站点的呼叫之间插入一点呼吸。

答案 3 :(得分:2)

每个线程都使用自己的变量副本运行。

但是我会修改我的appoarch。使用找到的变量不是线程安全的。一次可能发现多个线程正在发生变化。一个线程也可能正在读取它而另一个线程正在写入它。 [lock][1]可以避免这种情况。

解决此问题的更好方法是使用EventWaitHandle。这样你就不必担心锁定,你可以建立一个睡眠或超时,所以如果'claim-all'没有出现你的线程将不会比你想要的更长。

internal class ExampleOnExecute
{
    private static EventWaitHandle _stopEvent;

    public static EventWaitHandle StopEvent
    {
        get { return _stopEvent ?? (_stopEvent = new EventWaitHandle(false, EventResetMode.ManualReset)); }
    }

    public static void SpinOffThreads(IEnumerable<object> someCollection)
    {
        foreach(var item in someCollection)
        {
            // You probably do not want to manualy create a thread since these ideally would be small workers
            // and action BeingInvoke runs in the ThreadPool
            Action<object> process = BusyWait;

            process.BeginInvoke(item, null, null);
        }
    }

    private static void BusyWait(object obj)
    {
        // You can wait for however long you like or 0 is not waiting at all
        const int sleepAmount = 1;

        //     Blocks the current thread until the current instance receives a signal, using
        //     a System.TimeSpan to specify the time interval.
        //
        // Parameters:
        //   timeout:
        //     A System.TimeSpan that represents the number of milliseconds to wait, or
        //     a System.TimeSpan that represents -1 milliseconds to wait indefinitely.
        //
        // Returns:
        //     true if the current instance receives a signal; otherwise, false.
        while (!StopEvent.WaitOne(TimeSpan.FromMilliseconds(sleepAmount)))
        {
            // Do you work here
            var foundIt = DidIFindIt();

            if (foundIt)
            {
                // Signal all threads now to stop working we found it.
                StopEvent.Set();
            }
        }
    }

    private static bool DidIFindIt()
    {
        return true;
    }
}

Herethreading上出色的免费书。

相关问题