如何在超时后取消任务等待

丹·库克

我正在使用此方法以编程方式实例化Web浏览器,导航到url并在文档完成后返回结果。

如果文档加载时间超过5秒,我如何能够停止TaskGetFinalUrl()返回null

我已经看到许多使用a的示例,TaskFactory但是我无法将其应用于此代码。

 private Uri GetFinalUrl(PortalMerchant portalMerchant)
    {
        SetBrowserFeatureControl();
        Uri finalUri = null;
        if (string.IsNullOrEmpty(portalMerchant.Url))
        {
            return null;
        }
        Uri trackingUrl = new Uri(portalMerchant.Url);
        var task = MessageLoopWorker.Run(DoWorkAsync, trackingUrl);
        task.Wait();
        if (!String.IsNullOrEmpty(task.Result.ToString()))
        {
            return new Uri(task.Result.ToString());
        }
        else
        {
            throw new Exception("Parsing Failed");
        }
    }

// by Noseratio - http://stackoverflow.com/users/1768303/noseratio    

static async Task<object> DoWorkAsync(object[] args)
{
    _threadCount++;
    Console.WriteLine("Thread count:" + _threadCount);
    Uri retVal = null;
    var wb = new WebBrowser();
    wb.ScriptErrorsSuppressed = true;

    TaskCompletionSource<bool> tcs = null;
    WebBrowserDocumentCompletedEventHandler documentCompletedHandler = (s, e) => tcs.TrySetResult(true);

    foreach (var url in args)
    {
        tcs = new TaskCompletionSource<bool>();
        wb.DocumentCompleted += documentCompletedHandler;
        try
        {
            wb.Navigate(url.ToString());
            await tcs.Task;
        }
        finally
        {
            wb.DocumentCompleted -= documentCompletedHandler;
        }

        retVal = wb.Url;
        wb.Dispose();
        return retVal;
    }
    return null;
}

public static class MessageLoopWorker
{
    #region Public static methods

    public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args)
    {
        var tcs = new TaskCompletionSource<object>();

        var thread = new Thread(() =>
        {
            EventHandler idleHandler = null;

            idleHandler = async (s, e) =>
            {
                // handle Application.Idle just once
                Application.Idle -= idleHandler;

                // return to the message loop
                await Task.Yield();

                // and continue asynchronously
                // propogate the result or exception
                try
                {
                    var result = await worker(args);
                    tcs.SetResult(result);
                }
                catch (Exception ex)
                {
                    tcs.SetException(ex);
                }

                // signal to exit the message loop
                // Application.Run will exit at this point
                Application.ExitThread();
            };

            // handle Application.Idle just once
            // to make sure we're inside the message loop
            // and SynchronizationContext has been correctly installed
            Application.Idle += idleHandler;
            Application.Run();
        });

        // set STA model for the new thread
        thread.SetApartmentState(ApartmentState.STA);

        // start the thread and await for the task
        thread.Start();
        try
        {
            return await tcs.Task;
        }
        finally
        {
            thread.Join();
        }
    }
    #endregion
}
鼻塞

已更新WebBrowser可以在Github找到基于的控制台Web scraper的最新版本

已更新添加WebBrowser对象池以进行多个并行下载。

您是否有机会在控制台应用程序中执行此操作的示例?另外,我也不认为webBrowser可以是一个类变量,因为我要为每个变量并行运行整个过程,从而迭代成千上万个URL

以下是或多或少WebBrowser基于通用Web的scraper的实现,可作为控制台应用程序使用。这是我以前WebBrowser相关工作的合并,包括问题中引用的代码:

几点:

  • 可重用的MessageLoopApartment类用于启动和运行带有自己的消息泵的WinForms STA线程。可以从控制台应用程序使用它,如下所示。此类公开了TPL任务计划程序(FromCurrentSynchronizationContext)和一组Task.Factory.StartNew包装程序以使用此任务计划程序。

  • 这是在单独的STA线程上async/await运行WebBrowser导航任务的绝佳工具这样,WebBrowser就可以在该线程上创建,导航和销毁对象。虽然,MessageLoopApartment不是WebBrowser专门捆绑的

  • 使用浏览器功能控件启用HTML5渲染很重要,否则WebBrowser默认情况下对象将以IE7仿真模式运行。这就是SetFeatureBrowserEmulation下面的内容。

  • 不一定总是可以确定网页何时以100%的概率完成渲染。一些页面非常复杂,并使用连续的AJAX更新。但是,通过DocumentCompleted首先处理事件,然后轮询页面的当前HTML快照以查找更改并检查WebBrowser.IsBusy属性,我们可以得出非常接近结论。这就是NavigateAsync下面的内容。

  • 如果页面渲染永无休止(请注意CancellationTokenSourceCreateLinkedTokenSource,则上述内容中会出现超时逻辑

using Microsoft.Win32;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Console_22239357
{
    class Program
    {
        // by Noseratio - https://stackoverflow.com/a/22262976/1768303

        // main logic
        static async Task ScrapSitesAsync(string[] urls, CancellationToken token)
        {
            using (var apartment = new MessageLoopApartment())
            {
                // create WebBrowser inside MessageLoopApartment
                var webBrowser = apartment.Invoke(() => new WebBrowser());
                try
                {
                    foreach (var url in urls)
                    {
                        Console.WriteLine("URL:\n" + url);

                        // cancel in 30s or when the main token is signalled
                        var navigationCts = CancellationTokenSource.CreateLinkedTokenSource(token);
                        navigationCts.CancelAfter((int)TimeSpan.FromSeconds(30).TotalMilliseconds);
                        var navigationToken = navigationCts.Token;

                        // run the navigation task inside MessageLoopApartment
                        string html = await apartment.Run(() =>
                            webBrowser.NavigateAsync(url, navigationToken), navigationToken);

                        Console.WriteLine("HTML:\n" + html);
                    }
                }
                finally
                {
                    // dispose of WebBrowser inside MessageLoopApartment
                    apartment.Invoke(() => webBrowser.Dispose());
                }
            }
        }

        // entry point
        static void Main(string[] args)
        {
            try
            {
                WebBrowserExt.SetFeatureBrowserEmulation(); // enable HTML5

                var cts = new CancellationTokenSource((int)TimeSpan.FromMinutes(3).TotalMilliseconds);

                var task = ScrapSitesAsync(
                    new[] { "http://example.com", "http://example.org", "http://example.net" },
                    cts.Token);

                task.Wait();

                Console.WriteLine("Press Enter to exit...");
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                while (ex is AggregateException && ex.InnerException != null)
                    ex = ex.InnerException;
                Console.WriteLine(ex.Message);
                Environment.Exit(-1);
            }
        }
    }

    /// <summary>
    /// WebBrowserExt - WebBrowser extensions
    /// by Noseratio - https://stackoverflow.com/a/22262976/1768303
    /// </summary>
    public static class WebBrowserExt
    {
        const int POLL_DELAY = 500;

        // navigate and download 
        public static async Task<string> NavigateAsync(this WebBrowser webBrowser, string url, CancellationToken token)
        {
            // navigate and await DocumentCompleted
            var tcs = new TaskCompletionSource<bool>();
            WebBrowserDocumentCompletedEventHandler handler = (s, arg) =>
                tcs.TrySetResult(true);

            using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
            {
                webBrowser.DocumentCompleted += handler;
                try
                {
                    webBrowser.Navigate(url);
                    await tcs.Task; // wait for DocumentCompleted
                }
                finally
                {
                    webBrowser.DocumentCompleted -= handler;
                }
            }

            // get the root element
            var documentElement = webBrowser.Document.GetElementsByTagName("html")[0];

            // poll the current HTML for changes asynchronosly
            var html = documentElement.OuterHtml;
            while (true)
            {
                // wait asynchronously, this will throw if cancellation requested
                await Task.Delay(POLL_DELAY, token);

                // continue polling if the WebBrowser is still busy
                if (webBrowser.IsBusy)
                    continue;

                var htmlNow = documentElement.OuterHtml;
                if (html == htmlNow)
                    break; // no changes detected, end the poll loop

                html = htmlNow;
            }

            // consider the page fully rendered 
            token.ThrowIfCancellationRequested();
            return html;
        }

        // enable HTML5 (assuming we're running IE10+)
        // more info: https://stackoverflow.com/a/18333982/1768303
        public static void SetFeatureBrowserEmulation()
        {
            if (System.ComponentModel.LicenseManager.UsageMode != System.ComponentModel.LicenseUsageMode.Runtime)
                return;
            var appName = System.IO.Path.GetFileName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
            Registry.SetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION",
                appName, 10000, RegistryValueKind.DWord);
        }
    }

    /// <summary>
    /// MessageLoopApartment
    /// STA thread with message pump for serial execution of tasks
    /// by Noseratio - https://stackoverflow.com/a/22262976/1768303
    /// </summary>
    public class MessageLoopApartment : IDisposable
    {
        Thread _thread; // the STA thread

        TaskScheduler _taskScheduler; // the STA thread's task scheduler

        public TaskScheduler TaskScheduler { get { return _taskScheduler; } }

        /// <summary>MessageLoopApartment constructor</summary>
        public MessageLoopApartment()
        {
            var tcs = new TaskCompletionSource<TaskScheduler>();

            // start an STA thread and gets a task scheduler
            _thread = new Thread(startArg =>
            {
                EventHandler idleHandler = null;

                idleHandler = (s, e) =>
                {
                    // handle Application.Idle just once
                    Application.Idle -= idleHandler;
                    // return the task scheduler
                    tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
                };

                // handle Application.Idle just once
                // to make sure we're inside the message loop
                // and SynchronizationContext has been correctly installed
                Application.Idle += idleHandler;
                Application.Run();
            });

            _thread.SetApartmentState(ApartmentState.STA);
            _thread.IsBackground = true;
            _thread.Start();
            _taskScheduler = tcs.Task.Result;
        }

        /// <summary>shutdown the STA thread</summary>
        public void Dispose()
        {
            if (_taskScheduler != null)
            {
                var taskScheduler = _taskScheduler;
                _taskScheduler = null;

                // execute Application.ExitThread() on the STA thread
                Task.Factory.StartNew(
                    () => Application.ExitThread(),
                    CancellationToken.None,
                    TaskCreationOptions.None,
                    taskScheduler).Wait();

                _thread.Join();
                _thread = null;
            }
        }

        /// <summary>Task.Factory.StartNew wrappers</summary>
        public void Invoke(Action action)
        {
            Task.Factory.StartNew(action,
                CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Wait();
        }

        public TResult Invoke<TResult>(Func<TResult> action)
        {
            return Task.Factory.StartNew(action,
                CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Result;
        }

        public Task Run(Action action, CancellationToken token)
        {
            return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
        }

        public Task<TResult> Run<TResult>(Func<TResult> action, CancellationToken token)
        {
            return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
        }

        public Task Run(Func<Task> action, CancellationToken token)
        {
            return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
        }

        public Task<TResult> Run<TResult>(Func<Task<TResult>> action, CancellationToken token)
        {
            return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
        }
    }
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

asyncio等待多个任务超时和取消

来自分类Dev

超时后如何取消线程

来自分类Dev

如何在超时后中止multiprocessing.Pool中的任务?

来自分类Dev

如何在超时的情况下取消龙卷风中悬挂的异步任务?

来自分类Dev

等待任务超时

来自分类Dev

在执行器中从内部取消任务,或在超时后从外部取消任务

来自分类Dev

在执行器中从内部取消任务,或在超时后从外部取消任务

来自分类Dev

如何-具有超时和取消功能的多个异步任务

来自分类Dev

如何在JavaFx中设置任务超时?

来自分类Dev

如何在超时时间内等待ThreadPoolExecutor中的所有任务完成而又不关闭Executor呢?

来自分类Dev

等待某些任务可能被取消的多个任务

来自分类Dev

如何在超时后重置sql字段

来自分类Dev

设备断开连接后如何取消BLE任务?

来自分类Dev

如何在Android中设置可取消的超时

来自分类Dev

在RxJava中取消超时任务

来自分类Dev

指定的超时后任务未取消

来自分类Dev

如何在c / c ++中等待孩子达到CPU超时?

来自分类Dev

如何在Powershell中等待并终止超时过程

来自分类Dev

如何在Amazon Redshift中设置队列的等待超时?

来自分类Dev

如何在JmsMessagingTemplate.sendAndReceive上设置等待超时

来自分类Dev

如何在c / c ++中等待孩子达到CPU超时?

来自分类Dev

停止Redistributor时等待任务完成超时

来自分类Dev

如何等待C#中的单个事件以及超时和取消

来自分类Dev

如何在PLINQ中等待取消令牌注册方法

来自分类Dev

取消任务并等待其终止的选项

来自分类Dev

在Java中使用ThreadPoolExecutor时如何通过超时取消特定任务?

来自分类Dev

我如何知道是通过超时还是手动触发取消了任务?

来自分类Dev

取消任务后,继续任务不执行

来自分类Dev

如何取消挂起的任务?

Related 相关文章

  1. 1

    asyncio等待多个任务超时和取消

  2. 2

    超时后如何取消线程

  3. 3

    如何在超时后中止multiprocessing.Pool中的任务?

  4. 4

    如何在超时的情况下取消龙卷风中悬挂的异步任务?

  5. 5

    等待任务超时

  6. 6

    在执行器中从内部取消任务,或在超时后从外部取消任务

  7. 7

    在执行器中从内部取消任务,或在超时后从外部取消任务

  8. 8

    如何-具有超时和取消功能的多个异步任务

  9. 9

    如何在JavaFx中设置任务超时?

  10. 10

    如何在超时时间内等待ThreadPoolExecutor中的所有任务完成而又不关闭Executor呢?

  11. 11

    等待某些任务可能被取消的多个任务

  12. 12

    如何在超时后重置sql字段

  13. 13

    设备断开连接后如何取消BLE任务?

  14. 14

    如何在Android中设置可取消的超时

  15. 15

    在RxJava中取消超时任务

  16. 16

    指定的超时后任务未取消

  17. 17

    如何在c / c ++中等待孩子达到CPU超时?

  18. 18

    如何在Powershell中等待并终止超时过程

  19. 19

    如何在Amazon Redshift中设置队列的等待超时?

  20. 20

    如何在JmsMessagingTemplate.sendAndReceive上设置等待超时

  21. 21

    如何在c / c ++中等待孩子达到CPU超时?

  22. 22

    停止Redistributor时等待任务完成超时

  23. 23

    如何等待C#中的单个事件以及超时和取消

  24. 24

    如何在PLINQ中等待取消令牌注册方法

  25. 25

    取消任务并等待其终止的选项

  26. 26

    在Java中使用ThreadPoolExecutor时如何通过超时取消特定任务?

  27. 27

    我如何知道是通过超时还是手动触发取消了任务?

  28. 28

    取消任务后,继续任务不执行

  29. 29

    如何取消挂起的任务?

热门标签

归档