使用BackgroundWorker定期调用函数

时间:2014-08-29 10:56:50

标签: c# multithreading asynchronous backgroundworker

我有一个C#windows窗体应用程序。我想通过从网络上获取信息来更新一些标签。我想使用BackgroundWorker定期调用函数。

public partial class OptionDetails : Form
{
    static System.ComponentModel.BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
    static void fun()
    {
       worker.DoWork += new DoWorkEventHandler(worker_DoWork);
       worker.RunWorkerCompleted += worker_RunWorkerCompleted;
       worker.WorkerSupportsCancellation = true;
       worker.RunWorkerAsync();
    }
    static void worker_DoWork(object sender, DoWorkEventArgs e)
    { // some work }

    static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    { // on completion }
}

如果我使用Timer,则UI会挂起。如何使用BackgroundWorker定期致电worker_DoWork

我的实际代码:

public partial class myform: Form
{
    public myform()
    {
        InitializeComponent();
    }

    public async Task Periodic(Action func, TimeSpan period, CancellationToken token)
    {
        while (true)
        {
            // throws an exception if someone has requested cancellation via the token.
            token.ThrowIfCancellationRequested();

            func();

            // asynchronously wait 
            await Task.Delay(period);
        }
    }

    public async void hello()
    {
        await Periodic(getCurrentInfo, TimeSpan.FromSeconds(2), CancellationToken.None);
    }

    private void myform_Load(object sender, EventArgs e)
    {
        hello();
    }


    private void getCurrentInfo()
    {
        WebDataRetriever wdr = new WebDataRetriever();
        string name = "name";
        string url = String.Empty;
        string[] prices = new string[2];
        bool urlExists = url.TryGetValue(name, out url);
        if (urlExists)
        {
            wdr.processurl();  // time consuming function
            prices[0] = wdr.price1;
            prices[1] = wdr.price2;

            System.Globalization.NumberFormatInfo nfo = new System.Globalization.CultureInfo("en-US", false).NumberFormat;
            if (prices != null)
            {
                // change labels
            }
        }
    }

}

1 个答案:

答案 0 :(得分:1)

您需要的最简单的解决方案可能是使用Timer启动BackgroundWorker,但使用async / await我相信会产生更紧凑和优雅的解决方案。

下面的解决方案是一个异步方法,编译器为其生成状态机。当调用异步方法Periodic时,它开始执行直到第一个await语句。在这种情况下,这是:

 await Task.Delay(period);

等待的表达式返回一个等待的,在这种情况下为Task,但它可以是anything,其方法GetAwaiter返回实现{{1}的类型接口或INotifyCompletion接口。

如果此任务完成,则该方法将继续同步执行,如果任务未完成,则该方法返回。任务完成后,方法的执行将在同一ICriticalNotifyCompletion中的await语句之后继续执行。如果你从GUI线程调用它,执行将在GUI线程中恢复,但对于你的情况,它将在SynchronizationContext线程上恢复,因为console apps do not have a SynchronizationContext

ThreadPool会返回Task.Delay(period)Task会在period过去时完成。即它就像Thread.Sleep的异步版本,因此while循环的执行会在period到期后继续执行。

最终结果是循环运行永远定期检查取消(在这种情况下抛出OperationCancelledException)并执行func

public static async Task Periodic(Action func, TimeSpan period, CancellationToken token)
{
    while(true)
    {
        // throws an exception if someone has requested cancellation via the token.
        token.ThrowIfCancellationRequested();

        func();

        // asynchronously wait 
        await Task.Delay(period);
    }
}

在GUI应用程序中,您可以像这样使用它:

await Periodic(() => /* do something clever here*/, TimeSpan.FromSeconds(1), CancellationToken.None);

控制台应用程序的主要方法不能是异步的,因此您无法使用await,但您可以在结果上调用Wait()而无限期地运行它并阻止应用程序退出。

void Main()
{
    Periodic(() => Console.WriteLine("Hello World!"), TimeSpan.FromSeconds(1), CancellationToken.None).Wait();
}

在GUI应用中调用Wait()时必须小心,因为它可能会导致deadlock

<强>更新

如果你想要在后台运行一个昂贵的耗时功能,你可能会因Periodic超载async func而受益。

public async Task Periodic(Func<Task> func, TimeSpan period, CancellationToken token)
{
    while (true)
    {
        // throws an exception if someone has requested cancellation via the token.
        token.ThrowIfCancellationRequested();

        await func();

        // asynchronously wait 
        await Task.Delay(period);
    }
}