我有TextBox
个TextChanged
事件,如果文本框的文本代表现有文件,则会调用自定义事件。在这种情况下,有一个外部dll的调用,对文件进行一些处理,这可能需要一分钟才能完成。我也做了一些后处理,取决于这个方法返回给我的结果。目前,这阻止了我的UI,这是非常不受欢迎的。
我看到基本上有2个“选项”/场景。
TextChanged
调用)在UI线程上?Task.Run()
将整个自定义事件放入其自己的线程中。这里的缺点是,除了dll方法调用之外,在long方法之后,有相当多的UI元素受到getter / setter的影响。我可以根据适当的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相关的内容(省略重复代码和没有调用的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;
}
答案 0 :(得分:0)
这基本上就是你想要的。我在这里违反了一些最佳做法,但只是表明它并不复杂。要记住的一件事是,用户现在可以连续多次单击此按钮。您可以考虑在处理之前禁用它。或者您可以执行Monitor.TryEnter()
以确保它尚未运行。
private async void buttonProcess_Click(object sender, RoutedEventArgs e)
{
textBlockStatus.Text = "Processing...";
bool processed = await Task.Run(() => SlowRunningTask());
}
private bool SlowRunningTask()
{
Thread.Sleep(5000);
return true;
}