代码之家  ›  专栏  ›  技术社区  ›  Broots Waymb

在继续之前等待结果,并且不阻塞UI[重复]

  •  -1
  • Broots Waymb  · 技术社区  · 8 年前

    我有一个 TextBox 用一个 TextChanged

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

    1. 在自定义事件中,在继续事件之前,以某种方式等待dll调用完成,同时保持UI空闲。这似乎是我未经训练的多线程自我提出的最简单的想法,但从概念上讲,它也给我带来了危险:考虑到自定义事件本身(从 事件 )在UI线程上?
    2. 使用将整个自定义事件抛出到自己的线程中 Task.Run() InvokeRequired ,但如果有更正确的方法,我宁愿采用这种方法。

    我用上面的选项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;
    }
    
    1 回复  |  直到 8 年前
        1
  •  1
  •   zzxyz    8 年前

    这基本上就是你想要的。我在这里违反了一些最佳实践,但只是表明它并没有那么复杂。需要记住的一点是,用户现在可以连续多次单击此按钮。您可以考虑在处理之前禁用它。或者你可以做一个 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;
        }