如何将事件参数的属性与异步方法一起使用

本文关键字:异步方法 一起 属性 事件 参数 | 更新日期: 2023-09-27 18:21:00

如果我们想将属性写入控件,如何在异步方法中使用属性。当尝试在没有调用的情况下进行写入时,它会抛出异常。所以我想在Counter类中处理调用问题,而不是在Form1中。我为这个问题写了一个例子。在这段代码中,TextBox.Text行引发跨线程异常。

internal class Form1 : Form        
{
    public Form1()
    {
         InitializeComponent();
    }
     private void button1_Click(object sender, EventArgs e)
    {
        Counter myCounter = new Counter(1000);
        myCounter.IndexValueChanged += myCounter_IndexValueChanged;
        myCounter.StartCountAsync();
    }
    void myCounter_IndexValueChanged(object sender, IndexValueChangedEventArgs e)
    {
        textBox1.Text = e.Index.ToString();
    }
}
class Counter
{
    public delegate void IndexValueChangedEventHandler(object sender, IndexValueChangedEventArgs e);
    public event IndexValueChangedEventHandler IndexValueChanged;
    int _maxNumber;
    int _index;
    public Counter(int maxNumber)
    {
        _maxNumber = maxNumber;
    }
    public async void StartCountAsync()
    {
        await Task.Run(() =>
        {
            for (int i = 0; i < _maxNumber; i++)
            {
                _index = i;
                if (IndexValueChanged != null)
                    IndexValueChanged(this, new IndexValueChangedEventArgs(_index));
                Thread.Sleep(100);
            }
        });
    }
}
class IndexValueChangedEventArgs
{
    int indexNum;
    public IndexValueChangedEventArgs(int index)
    {
        indexNum = index;
    }
    public int Index
    {
        get { return indexNum; }
    }
}`

如何将事件参数的属性与异步方法一起使用

要在ui线程上调用委托,需要一个windows句柄。我不知道为什么要在Counter类中进行调用,但在我看来,最简单的方法是:给Counter实例一个对Form的引用,并在该引用上调用InvokeMethod。(从我的手机上写,所以很难添加代码示例,明天将编辑)

EDIT:但我确实认为Form有责任检查它是否在正确的线程中进行了更改,然后调用(Begin)Invoke本身。

编辑:这是你的柜台类,添加了"家长"参考:

class Counter
{
  public delegate void IndexValueChangedEventHandler(object sender, IndexValueChangedEventArgs e);
  public event IndexValueChangedEventHandler IndexValueChanged;
  int _maxNumber;
  int _index;
  Control _parent;
  public Counter(Control parent, int maxNumber)
  {
    _maxNumber = maxNumber;
    _parent = parent;
  }
  public async void StartCountAsync()
  {
    await Task.Run(() =>
    {
        for (int i = 0; i < _maxNumber; i++)
        {
            _index = i;
            // introduce local variable for thread safety
            IndexValueChangedEventHandler handler = IndexValueChanged;
            if (handler != null)
            {
                if (_parent == null || !_parent.InvokeRequired)
                    handler(this, new IndexValueChangedEventArgs(_index));
                else
                    // use BeginInvoke
                    _parent.BeginInvoke(handler, this, new IndexValueChangedEventArgs(_index));
            }
            Thread.Sleep(100);
        }
    });
  }
}

你在按钮处理程序中使用它,如下所示:

private void button1_Click(object sender, EventArgs e)
{
    Counter myCounter = new Counter(this, 1000);
    myCounter.IndexValueChanged += myCounter_IndexValueChanged;
    myCounter.StartCountAsync();
}

最好在Counter类中使用BeginInvoke,因为Invoke会等到UI线程执行委托,因此Counter的计数可能比预期的慢(或者web客户端使用网络资源的时间会比需要的长)。希望这对你有所帮助。

EDIT:引入了局部变量"handler"。否则,消费者可能会在您对事件进行null检查后取消注册该事件,并且在您调用该事件时该事件为null。

在您的特定示例中,首先使用后台线程对我来说没有意义。当然,如果您在UI线程上调用Thread.Sleep,由于明显的原因,这是不好的,但您不需要调用Thread.Sleep来获得等待的async方法。

public async void StartCountAsync()
{
    for (int i = 0; i < _maxNumber; i++)
    {
        _index = i;
        if (IndexValueChanged != null)
            IndexValueChanged(this, new IndexValueChangedEventArgs(_index));
        await Task.Delay(100);
    }
}

如果从UI线程调用,那么在延迟之后,该方法将继续在UI线程上执行,因此IndexValueChanged的调用将正常工作。

您可以更改应用程序的行为,以允许跨线程调用来修改表单元素:

System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false;

或者,如果人们说这是不推荐的、不安全的、不专业的,

您可以在Counter类中使用AsyncOperationManager。WebClient.DownloadFileAsync方法在内部使用相同的方法。这将确保在UI线程上调用事件处理程序。

这是您的最后一个Counter类,它应该在UI线程上调用委托。。

class Counter
{
    public delegate void IndexValueChangedEventHandler(object sender, IndexValueChangedEventArgs e);
    public event IndexValueChangedEventHandler IndexValueChanged;
    int _maxNumber;
    int _index;
    public Counter(int maxNumber)
    {
        _maxNumber = maxNumber;
    }
    public async void StartCountAsync()
    {
        AsyncOperation asyncCountOperation = AsyncOperationManager.CreateOperation(null);
        await Task.Run(() =>
        {
            for (int i = 0; i < _maxNumber; i++)
            {
                _index = i;
                asyncCountOperation.Post(new SendOrPostCallback(InvokeDelegate), _index);
                Thread.Sleep(100);
            }
        });
    }
    private void InvokeDelegate(object index)
    {
        if (IndexValueChanged != null)
        {
            IndexValueChanged(this, new IndexValueChangedEventArgs((int)index));
        }
    }
}