我有一个TextBox
与TextChanged
事件,如果文本框的文本代表现有文件,则会调用自定义事件。在这个事件中,有一个外部dll的调用,它会对File执行一些处理,这可能需要一分钟才能完成。我也有一些后期处理,这取决于这个方法返回给我的结果。目前,这阻止了我的用户界面,这是非常不可取的。在继续之前等待结果并且不会阻止UI
我看到基本上有2个“选项”/场景。
- 在自定义事件中,以某种方式等待dll调用完成,继续事件之前,同时保持UI自由。这似乎是我的多线程未经训练的自我中最简单的想法,但它也在概念上向我抛出红旗:鉴于自定义事件本身(从
TextChanged
调用)位于UI线程上,这甚至有可能吗? - 使用
Task.Run()
将整个自定义事件放入其自己的线程中。这里的缺点是,除了dll方法调用之外,在long方法之后,有相当多的受getters/setter影响的UI元素。我可以根据相应的InvokeRequired
编写替代的getter/setter,但如果有更正确的方法来做到这一点,我宁愿采取这种方法。
我做了一个短得多(虽然做作)的示例项目,其实质上显示我后,使用选项2从上面:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
comboBox1.Items.Add("Select One...");
comboBox1.Items.Add("Item 1");
comboBox1.Items.Add("Item 2");
Value = 0;
}
public string SetMessage
{
set
{
if (lblInfo.InvokeRequired)
lblInfo.BeginInvoke((MethodInvoker)delegate() { lblInfo.Text = Important ? value + "!" : value; });
else
lblInfo.Text = Important ? value + "!" : value;
}
}
public bool Important
{
get
{
return chkImportant.Checked;
}
set
{
if (chkImportant.InvokeRequired)
chkImportant.BeginInvoke((MethodInvoker) delegate() { chkImportant.Checked = value; });
else
chkImportant.Checked = value;
}
}
public SomeValue Value
{
get
{
if (comboBox1.InvokeRequired)
{
SomeValue v = (SomeValue)comboBox1.Invoke(new Func<SomeValue>(() => SomeValue.Bar));
return v;
}
else
{
switch (comboBox1.SelectedIndex)
{
case 1:
return SomeValue.Foo;
case 2:
return SomeValue.Bar;
default:
return SomeValue.Nothing;
}
}
}
set
{
if (comboBox1.InvokeRequired)
{
comboBox1.BeginInvoke((MethodInvoker)delegate()
{
switch (value)
{
case SomeValue.Nothing:
comboBox1.SelectedIndex = 0;
break;
case SomeValue.Foo:
comboBox1.SelectedIndex = 1;
break;
case SomeValue.Bar:
comboBox1.SelectedIndex = 2;
break;
}
});
}
else
{
switch (value)
{
case SomeValue.Nothing:
comboBox1.SelectedIndex = 0;
break;
case SomeValue.Foo:
comboBox1.SelectedIndex = 1;
break;
case SomeValue.Bar:
comboBox1.SelectedIndex = 2;
break;
}
}
}
}
private void CustomEvent(object sender, EventArgs e)
{
if (!Important)
Important = true;
SetMessage = "Doing some stuff";
if (Value == SomeValue.Foo)
Debug.WriteLine("Foo selected");
//I don't want to continue until a result is returned,
//but I don't want to block UI either.
if (ReturnsTrueEventually())
{
Debug.WriteLine("True!");
}
Important = false;
SetMessage = "Finished.";
}
public bool ReturnsTrueEventually()
{
//Simulates some long running method call in a dll.
//In reality, I would interpret an integer and return
//an appropriate T/F value based on it.
Thread.Sleep(5000);
return true;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
//Do I *need* to multithread the whole thing?
Task.Run(() => CustomEvent(this, new EventArgs()));
}
}
public enum SomeValue
{
Nothing = 0,
Foo = 100,
Bar = 200
}
注意:我不要求代码审查我的选项2代码。相反,我在问是否需要选项2来完成,因为这个选项会导致我改变相当大一部分代码,因为它只有1个方法阻止了整个过程。
我也意识到我可以简化这些属性中的一些代码来防止复制。为了向我自己展示和调试,我暂时搁置了这个问题。
这是我曾与选项1(左掉重复的代码和getter/setter方法没有他们所调用):
private async void CustomEvent(object sender, EventArgs e)
{
if (!Important)
Important = true;
SetMessage = "Doing some stuff";
if (Value == SomeValue.Foo)
Debug.WriteLine("Foo selected");
//I don't want to continue until a result is returned,
//but I don't want to block UI either.
if (await ReturnsTrueEventually())
{
Debug.WriteLine("True!");
}
Important = false;
SetMessage = "Finished.";
}
public async Task<bool> ReturnsTrueEventually()
{
//Simulates some long running method call in a dll.
//In reality, I would interpret an integer and
//return an appropriate T/F value based on it.
Thread.Sleep(5000);
return true;
}
如果你想比较两个选项,写出选项1,所以你可以看到它是如何不同。 – Servy
使用异步/等待和任务来保持你的UI响应。你使用的是什么版本的.NET框架? – Dido
@Servy - 当我这样做的时候,我基本上想出了一些以检查'awaitReturnTrueEventually()。'结果'为结尾,如果我理解正确的话,最终会阻止。当然,我意识到我可能完全采取错误的做法... –