【问题标题】:Call a function periodically using BackgroundWorker使用 BackgroundWorker 定期调用函数
【发布时间】:2014-08-29 10:56:50
【问题描述】:

我有一个 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
            }
        }
    }

}

【问题讨论】:

  • 我猜你用过System.Windows.Forms.timer。使用System.Threading.TimerSystem.Timers.Timer。它不会阻塞用户界面。
  • 您使用的是什么版本的 .NET?如果您使用的是 .NET 4.5,则可以使用 async/await 和 Task.Delay
  • @SriramSakthivel 最好也在这里提供有关 UI 线程问题的建议 msdn.microsoft.com/en-us/magazine/cc164015.aspx
  • 除了系统计时器,您可能还需要 ReportProgress 函数来更新 UI 线程中的标签:msdn.microsoft.com/en-us/library/…
  • 我不需要,@Steve 做到了。 :)

标签: c# multithreading asynchronous backgroundworker


【解决方案1】:

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

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

 await Task.Delay(period);

awaited 表达式返回一个 awaitable,在本例中为 Task,但它可以是 anything,它有一个方法 GetAwaiter,它返回一个实现 INotifyCompletion 接口或 ICriticalNotifyCompletion 接口的类型.

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

Task.Delay(period) 返回一个Task,当period 过去时,该Task 完成。 IE。它就像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);

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

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

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

更新

如果您想要在后台运行一个昂贵的耗时函数,您还可以从使用 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 不是很熟悉。
  • 我已经添加了我的代码。 GUI 现在可以响应了。在测试代​​码时,我不断地移动表单。表单会挂起几秒钟,然后再次响应。它挂起的时间间隔不是 2 秒。有时大约 1 秒,有时大约 5 秒。我不知道为什么会这样。
  • 我在耗时的函数中添加了等待和异步,现在表单不会挂起。但是,当我运行应用程序时,显示表单会有延迟。有什么好的方法可以消除这个吗?目前,我在表单的加载函数中调用函数hello
  • 您可以尝试在后台线程中使用Task.Run 运行昂贵的函数,或者尽可能使用async 制作昂贵的函数。许多网络检索函数都有异步重载。
猜你喜欢
  • 2021-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-19
  • 2018-01-26
相关资源
最近更新 更多