使用BackgroundWorker定时调用函数

4

我有一个C#的Windows窗体应用程序。我想根据从web获取的信息更新一些标签。我想使用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,界面会卡住。如何使用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
我猜你使用了 System.Windows.Forms.timer。请使用 System.Threading.TimerSystem.Timers.Timer。这样不会阻塞用户界面。 - Sriram Sakthivel
2
你使用的是哪个版本的.NET?如果你使用的是.NET 4.5,你可以使用async/await和Task.Delay - NeddySpaghetti
@SriramSakthivel - 一个WinForms计时器也不应该阻塞。 - H H
1
我不需要,@Steve已经做了。 :) - Sriram Sakthivel
请发布您的代码而不是//Some work等注释。或者至少发布一些模拟原始代码的代码。我会尽力帮忙。您使用的是哪个版本的.NET? - Sriram Sakthivel
显示剩余7条评论
1个回答

1
您需要的最简单的解决方案可能是使用Timer启动BackgroundWorker,但是使用async/await会产生更紧凑、更优雅的解决方案。
下面的解决方案是一个异步方法,编译器会生成一个状态机。当调用异步方法Periodic时,它开始执行直到第一个await语句。在这种情况下,它是:
 await Task.Delay(period);

表达式 `awaited` 返回一个可等待对象,这里是一个 `Task`,但它也可以是任何实现了 `INotifyCompletion` 接口或 `ICriticalNotifyCompletion` 接口的对象,比如 anything,该对象必须有一个名为 `GetAwaiter` 的方法。
如果这个任务已经完成,那么该方法会同步执行,否则该方法会返回。一旦任务完成,该方法会在相同的 SynchronizationContext 中从 await 语句后面继续执行。如果你在 GUI 线程中调用此方法,则会在 GUI 线程中恢复执行,但对于你的情况,它会在一个 ThreadPool 线程上恢复执行,因为 控制台应用程序没有 SynchronizationContext

Task.Delay(period) 返回一个 Task,当 period 时间过去后,该任务将会完成。也就是说,它类似于 Thread.Sleep 的异步版本,因此在 period 过期后,while 循环的执行将会恢复。

最终结果是循环永远运行,定期检查是否取消(如果取消,则抛出 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() 时必须小心,因为它可能会导致 死锁

更新

如果您有一个耗时的函数想要在后台运行,那么您还可以受益于具有接受 async funcPeriodic 重载。

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);
    }
}

我说过我有一个Windows窗体应用程序,但是我提供的是控制台应用程序的代码。我已经相应地更改了代码。让我试试你的解决方案。我是C#的新手,所以对await不是很熟悉。 - rako
我已经添加了我的代码。GUI现在是响应式的。我一直在测试代码时不断移动窗体。窗体会挂起几秒钟,然后再次变得响应。它挂起的时间间隔不是2秒。有时大约1秒,有时大约5秒。我不知道为什么会发生这种情况。 - rako
我在耗时函数中添加了awaits和asyncs,现在表单不会挂起。但是,当我运行应用程序时,显示表单会有延迟。有没有好的方法可以解决这个问题?目前,我正在表单的加载函数中调用函数“hello”。 - rako
你可以尝试使用Task.Run在后台线程中运行昂贵的函数,或者如果可能的话将昂贵的函数变成async。许多网络检索函数都有异步重载。 - NeddySpaghetti

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