线程中的忙等待

3

基本上,我需要忙碌等待直到网页上出现某些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个候选人,它会产生8个线程,每个线程都在寻找“网页”上出现的“全部声明”。 “found”是全局变量。一旦其中一个线程找到了“全部声明”,它们都应该退出。
对此方法我有几个问题。首先,这是一个好方法吗?第二,每个线程是否都会得到其自己的“繁忙等待函数”副本?我的意思是,一个线程是否可以抢占另一个线程并更改该函数中的数据,或者它们是否会获得在函数内声明的变量的副本?请注意,这两个函数都在同一个对象内。

1
这是一个有效的技术问题,但看起来像是在扑克牌或拍卖中作弊的尝试。 - H H
4个回答

5
在回答您的问题之前,我必须指出,您犯了严重的错误,即闭环变量
首先,这种方法并不好。随意创建线程通常不是一个好主意。最好使用线程池技术,可以使用ThreadPool.QueueUserWorkItemTask类来实现。
第二个问题是,每个线程是否会获得繁忙等待函数的“副本”。我的意思是,一个线程能否抢占另一个线程,并更改该函数中的数据,或者它们每个都获得在函数中声明的变量的副本。 BusyWait的每个运行实例都将获得所有本地变量的副本(即pagerequest)。由于found是在非本地范围内声明的(至少假设如此),因此它将在BusyWait的所有运行实例之间共享。因此,您当前对found的读取和写入不是线程安全的,因为没有放置同步机制。

谢谢你的回答。你可能解决了我看到的另一个 bug。只是为了我正确理解这篇文章。在我的 foreach 循环中,我需要创建一个新变量来保存候选人,然后将其传递给 BusyWait 函数? - user489041
好的,发现得很好,我完全没注意到。(霍默很不错,但可能有点过头了)。 - H H
@user489041:没错。闭包捕获的是变量而不是。当前形式下的foreach循环只为整个循环创建一个变量。这就是为什么你需要自己创建一个单独的变量来使其正常工作。C# 5.0将改变foreach循环的行为,以便在每次迭代时自动创建一个单独的变量 - Brian Gideon

2

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

但是,我会稍微修改我的方法。使用found变量不是线程安全的。可能会有多个线程同时更改found变量。也很可能一个线程在读取它时另一个线程在写入它。使用锁可以避免这种情况。

解决这个问题的更好方法是使用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;
    }
}

这里有一本关于线程的优秀书籍,而且是免费的。


2

第二点,每个线程是否都会获得其自己的"副本"繁忙等待函数?

每个线程将使用其自己的堆栈空间执行该函数,这意味着函数内部的任何局部变量都属于运行该函数的线程。如果您有一个全局变量,例如在函数内被修改的found变量,则需要设置同步机制,以便不会同时从多个线程访问它,否则将导致难以找到的错误和一堆你永远不想想象的恐怖!


2

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

你共享的found变量应该声明为易失性变量。

这是一个罕见的情况,调用Thread.Sleep()可能会起到一些作用。在对同一个站点进行调用之间插入一点休息时间。


我其实考虑过这样做。但是,您能详细说明添加Thread.Sleep()的好处吗?我的应用程序实际上正在与其他应用程序竞争。它非常重要,它首先检测到“全部声明”按钮。添加Thread.Sleep()会对此情况有帮助还是有害?谢谢。 - user489041
短暂的Sleep()可以缓解系统的负担,使得处理8个任务更加均衡。但是,如果要在最后一毫秒内保持竞争力,请不要使用Sleep()。 - H H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接