如何将事件参数的属性与异步方法一起使用
本文关键字:异步方法 一起 属性 事件 参数 | 更新日期: 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));
}
}
}