2017-08-04 64 views
-2

我有一个TextBoxTextChanged事件,如果文本框的文本代表现有文件,则会调用自定义事件。在这个事件中,有一个外部dll的调用,它会对File执行一些处理,这可能需要一分钟才能完成。我也有一些后期处理,这取决于这个方法返回给我的结果。目前,这阻止了我的用户界面,这是非常不可取的。在继续之前等待结果并且不会阻止UI

我看到基本上有2个“选项”/场景。

  1. 在自定义事件中,以某种方式等待dll调用完成,继续事件之前,同时保持UI自由。这似乎是我的多线程未经训练的自我中最简单的想法,但它也在概念上向我抛出红旗:鉴于自定义事件本身(从TextChanged调用)位于UI线程上,这甚至有可能吗?
  2. 使用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; 
} 
+0

如果你想比较两个选项,写出选项1,所以你可以看到它是如何不同。 – Servy

+0

使用异步/等待和任务来保持你的UI响应。你使用的是什么版本的.NET框架? – Dido

+0

@Servy - 当我这样做的时候,我基本上想出了一些以检查'awaitReturnTrueEventually()。'结果'为结尾,如果我理解正确的话,最终会阻止。当然,我意识到我可能完全采取错误的做法... –

回答

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; 
    } 
+0

耶稣......我很确定我尝试过这个,但是在Task.Run之前忘了等待(或者把它放在其他地方,我不记得了)。我正在面对现在的相当困难... –

+0

请记住,您正在等待的不是Task.Run(),但它是返回值,在这种情况下是任务。所以你可以做'任务 processedTask = Task.Run(...);'然后当你需要结果时:'bool processed = await processedTask;' – zzxyz

相关问题