提问人:PharoeJack 提问时间:12/11/2022 最后编辑:PharoeJack 更新时间:12/13/2022 访问量:105
如何从两个单独的异步函数异步调用同一窗体上的两个单独的控件?
How can I invoke two separate controls on the same form asynchronously from two separate async functions?
问:
我正在开发一个用于股票和图表的 C# 软件。我有一个类“Stock”,它有一个类“Candles”的列表。Stock 类从在线交易所接收一根蜡烛,该蜡烛具有 4 个小数属性,开盘价、最高价、最低价和收盘价。Stock 类创建一个蜡烛并更新其属性。它还会更新股票的当前价格。然后,当属性更改时,它会引发两个事件。蜡烛的一个事件发生了变化,价格的另一个事件发生了变化。
当事件被引发时,我有一个表单“详细信息”,它应该更新烛台图和价格标签。
数据每隔几毫秒就会出现一次,但出于某种原因,我无法理解只有图表更新,而价格标签保持冻结状态。如果我暂停禁用更新图表的事件,则价格标签会更新。为什么两个控件不同时更新?
代码如下:
private decimal _currentPrice { get; set; }
public decimal currentPrice
{
get
{
return _currentPrice;
}
set
{
if (value != _currentPrice)
{
_currentPrice = value;
OnPriceChanged(value);
}
}
}
public event Action<decimal> PriceChanged;
public event Action<Candles> LastCandleChanged;
protected virtual void OnPriceChanged(decimal price)
{
if (PriceChanged != null)
{
PriceChanged(price);
}
}
protected virtual void OnLastCandleChanged(Candles candle)
{
if (LastCandleChanged != null)
{
LastCandleChanged(candle);
}
}
当我更新开盘价、最高价、最低价和收盘价的所有价格时,我调用以下内容:
OnLastCandleChanged(candle);
public event System.EventHandler<Candles> LastCandleChanged;
protected virtual void OnLastCandleChanged(Candles candle) {
if (LastCandleChanged != null) {
LastCandleChanged(this, candle);
}
}
在名为“DetailsForm”的窗体上,我在加载表单时创建了两个事件侦听器。它们调用更新函数。
stock.PriceChanged += async (sender1, args) => await updatePrice(sender1, args);
stock.LastCandleChanged += async (sender2, args) => await updateLastCandle(sender2, args);
public async Task updatePrice(object sender, decimal price) {
if (this.Visible) // to avoid invoking when form is closed
{
await Task.Run(() => {
this.BeginInvoke(new Action(() => {
priceLbl.Text = price.ToString();
}));
});
}
}
public async Task updateLastCandle(object sender, Candles candle) {
if (this.Visible) // to avoid invoking when form is closed
{
await Task.Run(() => {
this.BeginInvoke(new Action(() => {
updateChart(candle);
}));
});
}
}
为什么这两个控件不同时更新?
更新图表的代码:
public void updateChart(Candles candle){
//these 4 variables are declared in class level
close = candle.close;
open = candle.open;
high = candle.high;
low = candle.low;
if (candle == lastCandle)
{
detailedChart.Series["Candles2"].Points.RemoveAt(xPoint);
if (candle.close > candle.open)
{
detailedChart.Series["Candles2"].Points.AddXY(xPoint, low, high,
open, close);
detailedChart.Series["Candles2"].Points[xPoint].Color =
Color.PaleGreen;
}
else
{
detailedChart.Series["Candles2"].Points.AddXY(xPoint, low, high,
close, open);
detailedChart.Series["Candles2"].Points[xPoint].Color =
Color.LightPink;
}
}
else if (candle.index > lastCandle.index)
{
lastCandle = candle;
xPoint++;
if (candle.close > candle.open)
{
detailedChart.Series["Candles2"].Points.AddXY(xPoint, low,
high, open, close);
detailedChart.Series["Candles2"].Points[xPoint].Color =
Color.PaleGreen;
}
else
{
detailedChart.Series["Candles2"].Points.AddXY(xPoint, low,
high, close, open);
detailedChart.Series["Candles2"].Points[xPoint].Color =
Color.LightPink;
}
//update scale
detailedChart.ChartAreas["ChartArea1"].AxisX.Maximum += 1;
detailedChart.ChartAreas["ChartArea1"].AxisX.Minimum += 1;
detailedChart.ChartAreas["ChartArea1"].RecalculateAxesScale();
}
}
答:
从你展示的代码中不可能说出为什么会发生这种情况,但有一些“代码味道”:一些看起来很奇怪的东西可能会或可能不会做出贡献。
例如:
stock.PriceChanged += async (sender1, args) => await updatePrice(sender1, args);
stock.LastCandleChanged += async (sender2, args) => await updateLastCandle(sender2, args);
在这些行中,您将创建一个匿名方法(使用 )来调用单个异步方法。这会不必要地向调用堆栈添加一个方法,但随之而来的是使额外的异步方法工作所需的所有计算。最好将这些事件处理程序方法声明为(唯一可接受的用途是事件处理程序),然后直接使用它们。async void
async (sender1, args)
async void
async void
然后还有这个,这就是Paulo在评论中谈到的“不必要的上下文更改和编组”:
await Task.Run(() => {
this.BeginInvoke(new Action(() => {
priceLbl.Text = price.ToString();
}));
});
调用 Task.Run()
会告诉它在不同的线程中运行代码,但随后会立即使用它来告诉它在 UI 线程中运行代码。因此,这两个调用都变得毫无用处,因为您最终会回到从中开始的同一线程(UI 线程)上。BeginInvoke()
应用这两个建议,你最终会得到这个:
stock.PriceChanged += updatePrice;
stock.LastCandleChanged += updateLastCandle;
public async void updatePrice(object sender, decimal price) {
if (this.Visible) // to avoid invoking when form is closed
{
priceLbl.Text = price.ToString();
}
}
public async void updateLastCandle(object sender, Candles candle) {
if (this.Visible) // to avoid invoking when form is closed
{
updateChart();
}
}
Paulo 要求实施的原因是因为其中可能有什么东西阻止了文本价格的更新,因为你说当图表更新时文本会停止更新。updateChart()
评论
Task.Run()
上一个:委托和事件之间有什么区别?
下一个:创建一个返回泛型函数的函数
评论
updateChart