使用 Timer 和 Thread、股票应用程序更新 Winforms 标签
Posted
技术标签:
【中文标题】使用 Timer 和 Thread、股票应用程序更新 Winforms 标签【英文标题】:Updating Winforms Label with Timer and Thread, stock app 【发布时间】:2021-12-27 18:19:42 【问题描述】:之前可能有人问过它的要点,但我完全迷路了,所以我正在寻找一些个人指导。一直在尝试使用 WinForms 和 Yahoo API 为有趣的人制作股票跟踪器应用程序。尝试获取它,以便您可以输入跟踪器符号,它将创建一个新的标签,该标签将不时地自我更新。但是,它不断给我有关“跨线程操作无效”的错误消息。我试图做一些谷歌搜索,但是,是的,完全迷路了。大部分代码都写到这里了,希望大家能看懂。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using YahooFinanceApi;
namespace stockpoging4
public partial class Form1 : Form
public Form1()
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
private void button1_Click(object sender, EventArgs e)
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
do_Things(result);
public async Task<string> getStockPrices(string symbol)
try
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
catch
return "404";
public async void do_Things(string result)
string price;
Label label = null;
if (label == null)
price = await getStockPrices(result);
label = new Label() Name = result, Text = result + " $" + price ;
flowLayoutPanel2.Controls.Add(label);
else
Thread testThread = new Thread(async delegate ()
uiLockingTask();
price = await getStockPrices(result);
label.Text = result + " $" + price;
label.Update();
);
System.Timers.Timer timer = new System.Timers.Timer(10000);
timer.Start();
timer.Elapsed += do_Things(results);
private void uiLockingTask()
Thread.Sleep(5000);
【问题讨论】:
我在label == null
中看不到任何关系,并且在使用label
的线程上执行某些操作... 在 UI 线程上使用 async/await 创建一个简单的“定时器/循环” (没有阻塞)很容易。我试着举个例子。
【参考方案1】:
让我指出你的实现中的几件事。
-
您订阅 timer.Elapsed after timer.Start 可能在短时间间隔的情况下无效
在后台调用事件处理程序,这就是为什么您不断收到“跨线程操作无效”的原因。 UI 组件应该从后台线程正确调度,例如,通过调用 flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label)));和 label.BeginInvoke(new Action(label.Update))。此更改已经可以解决您的异常。
尽管我会以不同的方式实现此功能,但在这里我发布了一些稍作改动的代码,通过一些调整可以完全满足您的需求。
public partial class Form1 : Form
Task _runningTask;
CancellationTokenSource _cancellationToken;
public Form1()
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
private void button1_Click(object sender, EventArgs e)
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
do_Things(result);
_cancellationToken = new CancellationTokenSource();
_runningTask = StartTimer(() => do_Things(result), _cancellationToken);
private void onCancelClick()
_cancellationToken.Cancel();
public async Task<string> getStockPrices(string symbol)
try
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
catch
return "404";
private async Task StartTimer(Action action, CancellationTokenSource cancellationTokenSource)
try
while (!cancellationTokenSource.IsCancellationRequested)
await Task.Delay(1000, cancellationTokenSource.Token);
action();
catch (OperationCanceledException)
public async void do_Things(string result)
var price = await getStockPrices(result);
var label = new Label() Name = result, Text = result + " $" + price ;
flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label)));
【讨论】:
非常感谢!我刚刚复制了代码,它似乎在起作用,除了它不断打印新标签而不是更新现有标签。我将更好地查看代码以尝试理解/添加它,但无论如何都要感谢! 没问题。如果您将我的答案标记为正确,我会很高兴。【参考方案2】:如今,一种更简单的方法是使用异步。
这是一个每个间隔触发Action
的类:
public class UITimer : IDisposable
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// use a private function which returns a task
private async Task Innerloop(TimeSpan interval, Action<UITimer> action)
try
while (!_cancellationTokenSource.IsCancellationRequested)
await Task.Delay(interval, _cancellationTokenSource.Token);
action(this);
catch (OperationCanceledException)
// the constructor calls the private StartTimer, (the first part will run synchroniously, until the away delay)
public UITimer(TimeSpan interval, Action<UITimer> action) =>
_ = Innerloop(interval, action);
// make sure the while loop will stop.
public void Dispose() =>
_cancellationTokenSource?.Cancel();
如果您使用 dotnet 3.0 或更高版本,则可以使用 IAsyncDisposable。有了这个,您就可以等待 DisposeAsync
方法,因此您可以等待 _timerTask
完成。
我创建了一个新表单,并以此作为代码:
public partial class Form1 : Form
private readonly UITimer _uiTimer;
private int _counter;
public Form1()
InitializeComponent();
// setup the time and pass the callback action
_uiTimer = new UITimer(TimeSpan.FromSeconds(1), Update);
// the orgin timer is passed as parameter.
private void Update(UITimer timer)
// do your thing on the UI thread.
_counter++;
label1.Text= _counter.ToString();
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
// make sure the time (whileloop) is stopped.
_uiTimer.Dispose();
优点是,回调在 UI 线程上运行,但不会阻塞它。 await Task.Delay(..)
在后台使用 Timer,但在 UI 线程上发布其余的方法/状态机(因为 UI 线程有 SynchronizaionContext)
简单但成功;-)
【讨论】:
以上是关于使用 Timer 和 Thread、股票应用程序更新 Winforms 标签的主要内容,如果未能解决你的问题,请参考以下文章
Thread.Sleep 还是 Thread.Timer 挂机系统?