我有一个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
}
}
}
}
答案 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);
}
}