如何从两个单独的异步函数异步调用同一窗体上的两个单独的控件?

How can I invoke two separate controls on the same form asynchronously from two separate async functions?

提问人:PharoeJack 提问时间:12/11/2022 最后编辑:PharoeJack 更新时间:12/13/2022 访问量:105

问:

我正在开发一个用于股票和图表的 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();

}

}

enter image description here

C# 事件 async-await 委托调用

评论

1赞 MD Zand 12/11/2022
尝试添加更好的标签,不清楚您的UI是什么?Webform、Winform?WPF?...
0赞 PharoeJack 12/12/2022
WinForm 应用程序
1赞 Paulo Morgado 12/12/2022
代码过于复杂,并且具有不必要的上下文更改和编组。该方法的实现是什么?updateChart
0赞 PharoeJack 12/12/2022
updateChart 方法检查 Stock 类中 List<Candles> 中的最后一个 Candle。蜡烛对象包括开盘价、最高价、最低价和收盘价。如果蜡烛已更新,则它会删除 X 轴点处的最后一根蜡烛并创建一个新的图表蜡烛,然后将其添加到图表中。我没有包括它,因为它太长太复杂了,我认为它不相关。
0赞 PharoeJack 12/12/2022
保罗。请解释一下你说的不必要的上下文更改和编组是什么意思?我愿意从所有建议中学习。

答:

1赞 Gabriel Luci 12/12/2022 #1

从你展示的代码中不可能说出为什么会发生这种情况,但有一些“代码味道”:一些看起来很奇怪的东西可能会或可能不会做出贡献。

例如:

stock.PriceChanged += async (sender1, args) => await updatePrice(sender1, args);
stock.LastCandleChanged += async (sender2, args) => await updateLastCandle(sender2, args);

在这些行中,您将创建一个匿名方法(使用 )来调用单个异步方法。这会不必要地向调用堆栈添加一个方法,但随之而来的是使额外的异步方法工作所需的所有计算。最好将这些事件处理程序方法声明为(唯一可接受的用途是事件处理程序),然后直接使用它们。async voidasync (sender1, args)async voidasync 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()

评论

0赞 PharoeJack 12/13/2022
加布里埃尔。谢谢你的回答。我在原始问题中添加了用于更新图表的代码。1.如果我删除“BeginInvoke”,我会收到一个错误,因为调用是从不同的线程生成的,所以我得到一个跨线程异常。2. 我测试了您建议的代码,但它产生了错误,因为没有等待异步函数 (updateLastCandle...)。更新图表函数尝试在相同的索引(x 轴)上工作,而以前的异步函数仍在更新该索引,因此我得到“索引超出范围错误”。如果我完全删除异步,整个表单就会冻结。
0赞 Gabriel Luci 12/13/2022
“1.如果我删除“BeginInvoke”,我会收到一个错误,因为调用是从不同的线程生成的“ - 是的,这就是为什么你也需要删除它。Task.Run()
0赞 Gabriel Luci 12/13/2022
“2.我测试了您建议的代码,但它产生了错误,因为没有等待异步函数(updateLastCandle...)。- 您在哪一行看到该错误?