C# - 如何在一定时间内阻止方法返回?

4
我有一个Win Forms应用程序,其中包含一个Web浏览器控件。由于导航的异步特性,我需要能够在Web浏览器操作之间添加延迟。
Document_Complete事件是无用的,因为它没有考虑到页面可能包含多个AJAX请求。该事件经常会触发多次。
更新:
当页面加载时,将进行AJAX请求。因此,页面加载后通过HTTP请求获取某些DIV中的内容。因此,Document_Complete事件在文档首次加载时被触发,然后在每个(AJAX)HTTP请求返回时被触发。不好。
更新2:
我的应用程序尝试从Webbrowser.Document对象中读取HtmlElements。因为代码执行速度比HTTP请求返回快...所以文档对象不包含所有html元素。
我需要的是一种延迟主线程方法调用的方式。我已经尝试使用计时器:
private void startTimer()
        {
            timer.Interval = 2000;
            timer.Start();
            while (!BrowserIsReady)
            {
                //Wait for timer
            }
        }

这会锁定线程,导致计时事件无法触发。该循环永远不会结束。

我希望运行一系列像这样的方法:

Navagate("http://someurl.com");
//delay
ClickALink();
//delay
Navagate("Http://somewhere.com");
//delay

我能用计时器和BackgroundWorker解决这个问题吗?有人能提供一个可能的解决方案吗?


你在做什么需要阻塞执行,同时等待http请求返回?我问这个问题是因为设置计时器似乎不是一个好的方法。如果页面加载很快,用户被迫坐在那里更长的时间。或者如果页面非常缓慢,计时器认为它已经准备好了,但实际上还没有。 - taylonr
我正在等待页面加载完成。 - Nick
为什么不监听 DocumentCompleted 事件呢?这似乎比阻塞主线程更可取。 - Nathan Ernst
6个回答

1
你可以考虑使用Threading命名空间中的Monitor类。以下是示例。
using System;
using System.Threading;

namespace MonitorWait
{
    class Program
    {
        private static readonly object somelock = new object();

        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(SyncCall));

            Thread.Sleep(5000);  //Give the SyncCall a chance to run...
            //Thread.Sleep(6000);  //Uncomment to see it timeout.

            lock (somelock)
            {
                Monitor.Pulse(somelock);  //Tell the SyncCall it can wake up...
            }

            Thread.Sleep(1000);  //Pause the main thread so the other thread 
                                 //prints before this one :)

            Console.WriteLine("Press the any key...");
            Console.ReadKey();
        }

        private static void SyncCall(object o)
        {
            lock (somelock)
            {
                Console.WriteLine("Waiting with a 10 second timeout...");
                bool ret = Monitor.Wait(somelock, 10000);
                if (ret)
                {
                    Console.WriteLine("Pulsed...");
                }
                else
                {
                    Console.WriteLine("Timed out...");
                }
            }
        }
    }
}

0

我认为你可能遇到的问题之一(无法确定,因为代码不完整)是在需要调用时尝试在控件上执行操作。

我编写了一个小型表单,每五秒钟更改一次URL。请记住,在后台线程中查询Web浏览器状态也是不允许的。

  public partial class Form1 : Form
{
    System.Timers.Timer t = new System.Timers.Timer();
    Stack<string> urls = new Stack<string>();

    public Form1()
    {
        InitializeComponent();
        urls.Push("http://stackoverflow.com/");
        urls.Push("http://msdn.microsoft.com/");
        urls.Push("http://maps.google.com");


        t.AutoReset = false;
        t.Interval = 5000;
        t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);

    }



    void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {

         if (urls.Count > 0 )
        {
             string url = urls.Pop();
             SetUrl(url);

         }



         t.Interval = 5000;
        t.Start();

    }

    static void SynchronizedInvoke(ISynchronizeInvoke sync, Action action)
    {
        // If the invoke is not required, then invoke here and get out.
        if (!sync.InvokeRequired)
        {
            // Execute action.
            action();

            // Get out.
            return;
        }

        // Marshal to the required thread.
        sync.Invoke(action, new object[] { });
    }

    private void SetUrl(string url)
    {
        SynchronizedInvoke(webBrowser1, () => webBrowser1.Navigate(url));
    }



    private void button1_Click(object sender, EventArgs e)
    {
        t.Interval = 1;
        t.Start();
    }




}

0
感谢所有提供建议的人。这是我昨晚想出的“延迟”解决方案。虽然感觉像用蒸汽压路机去压花生,但我找不到更“优雅”的答案来解决这个特定的问题。
我正在启动一个新的工作线程,调用一个名为“AddDelay”的方法。这个方法将使工作线程在一段时间内进入睡眠状态。我的主(UI)线程在允许应用程序接收操作系统消息的同时,循环执行 Thread.IsAlive 条件,以确定该线程是否已完成。
看起来这样就能行了。
 private void Delay()
        {
            DelayTimer dt = new DelayTimer(1);
            Thread thread = new Thread(new ThreadStart(dt.AddDelay));
            thread.Start();
            while (thread.IsAlive)
            {
                Application.DoEvents();
            }

        }




public class DelayTimer
    {
        private int _seconds;
        public DelayTimer(int time)
        {
            _seconds = time;
        }
        public void AddDelay()
        {
           Thread.Sleep(_seconds*1000);
        }
    }

你是如何检测完成的?这只是一个被美化过的休眠语句。 - chilltemp

0

你不能简单地使用:

   System.Threading.Thread.Sleep(1000);

这应该有相同的效果,不是吗?


不,它不会。这会使主线程进入睡眠状态。浏览器正在主线程上运行。如果要让浏览器继续“工作”,延迟必须移出主线程。 - Nick

0

不幸的是,为了保持浏览器的功能性,您必须调用Application.DoEvents()。除此之外,我还会关注ReadyState属性。

  while (BusyNavigating || (wbBrowser.ReadyState != WebBrowserReadyState.Complete))
  {
    Application.DoEvents();
  } 

这些状态在 AJAX 请求中会多次更改。如何实现暂停代码执行,直到浏览器完成所有 HTTP 请求? - Nick
@nick:是什么导致了AJAX事件的触发?我使用这种技术在页面加载后或者在我调用按钮的点击方法时没有遇到问题。但如果AJAX事件是由定时器触发的,那你面临更大的挑战。你可能需要在循环中添加对页面元素的检查。抱歉,但网络爬虫是一项复杂的任务。 - chilltemp
Ajax请求由各种事件调用。我无法控制网页设计。我有兴趣在页面加载完成后获取DOM元素,例如用户点击链接 -> ReadyState可能在页面"完成"之前更改4-5次。似乎Document_Complete事件没有考虑到活动的http请求。 - Nick
@Nick,你的应用程序如何处理像 StackOverflow 这样每隔一段时间就有 AJAX 调用的情况? 停机问题:如何确定它是否会停止? - DevinB
@devinb 我相信有关页面将在几秒钟内完成。我找不到更优雅的方法来确保页面完成。我不确定如何实现这种延迟。 - Nick
显示剩余2条评论

0

Document_Complete会告诉你页面何时完成加载。

AJAX只是使其能够加载其他内容。某些Web应用程序(如StackOveflow)具有定时器上的AJAX调用,这意味着您的应用程序永远不会完全加载,因为始终存在一个等待进行/返回的AJAX调用。

这是一个问题,因为AJAX函数的回调可能会简单地启动另一个AJAX函数。除非解析整个页面(创建执行树),否则您无法确定页面何时真正完全加载。

也许您可以明确说明您的应用程序需要等待所有AJAX调用完成的原因,以及为什么等待Document_Complete还不够好。


@Nick,你知道Document_Complete事件会触发多少次吗?还是也是可变的。基本上,最好的方法仍然是依靠该事件。基本上,有没有办法从页面内容本身确定所有元素是否已加载完毕? - DevinB
你说得有道理。我会看看计算事件发生次数是否可行。 - Nick

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