C#

如何从另一个类中运行的另一个线程更新UI

发布于 2021-02-02 16:01:19

我目前正在用C#编写我的第一个程序,并且对这种语言非常陌生(到目前为止,仅用于C语言)。我做了很多研究,但是所有答案都太笼统了,我根本无法解决。

所以这是我的(很常见)问题:我有一个WPF应用程序,该应用程序从用户填写的几个文本框中获取输入,然后使用它们对它们进行大量计算。他们应该花费2-3分钟左右的时间,所以我想更新进度条和文本块,告诉我当前的状态是什么。另外,我还需要存储来自用户的UI输入并将其提供给线程,因此,我有一个第三类,用于创建对象并将该对象传递给后台线程。显然,我会在另一个线程中运行计算,因此UI不会冻结,但是我不知道如何更新UI,因为所有计算方法都是另一个类的一部分。经过大量的研究,我认为最好的方法是使用调度程序和TPL,而不是背景工作人员,

这是我程序的非常简单的结构:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        Initialize Component();
    }

    private void startCalc(object sender, RoutedEventArgs e)
    {
        inputValues input = new inputValues();

        calcClass calculations = new calcClass();

        try
        {
             input.pota = Convert.ToDouble(aVar.Text);
             input.potb = Convert.ToDouble(bVar.Text);
             input.potc = Convert.ToDouble(cVar.Text);
             input.potd = Convert.ToDouble(dVar.Text);
             input.potf = Convert.ToDouble(fVar.Text);
             input.potA = Convert.ToDouble(AVar.Text);
             input.potB = Convert.ToDouble(BVar.Text);
             input.initStart = Convert.ToDouble(initStart.Text);
             input.initEnd = Convert.ToDouble(initEnd.Text);
             input.inita = Convert.ToDouble(inita.Text);
             input.initb = Convert.ToDouble(initb.Text);
             input.initc = Convert.ToDouble(initb.Text);
         }
         catch
         {
             MessageBox.Show("Some input values are not of the expected Type.", "Wrong Input", MessageBoxButton.OK, MessageBoxImage.Error);
         }
         Thread calcthread = new Thread(new ParameterizedThreadStart(calculations.testMethod);
         calcthread.Start(input);
    }

public class inputValues
{
    public double pota, potb, potc, potd, potf, potA, potB;
    public double initStart, initEnd, inita, initb, initc;
}

public class calcClass
{
    public void testmethod(inputValues input)
    {
        Thread.CurrentThread.Priority = ThreadPriority.Lowest;
        int i;
        //the input object will be used somehow, but that doesn't matter for my problem
        for (i = 0; i < 1000; i++)
        {
            Thread.Sleep(10);
        }
    }
}

如果有人有一个简单的解释如何从测试方法中更新用户界面,我将不胜感激。由于我是C#和面向对象编程的新手,因此我很可能不会理解过于复杂的答案,但我会尽力而为。

同样,如果某人总体上有一个更好的主意(也许使用backgroundworker或其他工具),我也很乐意看到它。

关注者
0
被浏览
111
1 个回答
  • 面试哥
    面试哥 2021-02-02
    为面试而生,有面试问题,就找面试哥。

    首先,您需要使用Dispatcher.Invoke来从另一个线程更改UI,并从另一个类进行更改,您可以使用事件。
    然后,您可以在主类中注册该事件,然后将更改分发给UI,并在要通知UI的计算类中引发事件:

    class MainWindow : Window
    {
        private void startCalc()
        {
            //your code
            CalcClass calc = new CalcClass();
            calc.ProgressUpdate += (s, e) => {
                Dispatcher.Invoke((Action)delegate() { /* update UI */ });
            };
            Thread calcthread = new Thread(new ParameterizedThreadStart(calc.testMethod));
            calcthread.Start(input);
        }
    }
    
    class CalcClass
    {
        public event EventHandler ProgressUpdate;
    
        public void testMethod(object input)
        {
            //part 1
            if(ProgressUpdate != null)
                ProgressUpdate(this, new YourEventArgs(status));
            //part 2
        }
    }
    

    更新:
    看来这仍然是一个经常访问的问题,我想用现在的方式(使用.NET 4.5)更新此答案-这有点长,因为我将展示一些不同的可能性:

    class MainWindow : Window
    {
        Task calcTask = null;
    
        void buttonStartCalc_Clicked(object sender, EventArgs e) { StartCalc(); } // #1
        async void buttonDoCalc_Clicked(object sender, EventArgs e) // #2
        {
            await CalcAsync(); // #2
        }
    
        void StartCalc()
        {
            var calc = PrepareCalc();
            calcTask = Task.Run(() => calc.TestMethod(input)); // #3
        }
        Task CalcAsync()
        {
            var calc = PrepareCalc();
            return Task.Run(() => calc.TestMethod(input)); // #4
        }
        CalcClass PrepareCalc()
        {
            //your code
            var calc = new CalcClass();
            calc.ProgressUpdate += (s, e) => Dispatcher.Invoke((Action)delegate()
                {
                    // update UI
                });
            return calc;
        }
    }
    
    class CalcClass
    {
        public event EventHandler<EventArgs<YourStatus>> ProgressUpdate; // #5
    
        public TestMethod(InputValues input)
        {
            //part 1
            ProgressUpdate.Raise(this, status); // #6 - status is of type YourStatus
            //part 2
        }
    }
    
    static class EventExtensions
    {
        public static void Raise<T>(this EventHandler<EventArgs<T>> theEvent,
                                    object sender, T args)
        {
            if (theEvent != null)
                theEvent(sender, new EventArgs<T>(args));
        }
    }
    

    @ 1)如何启动“同步”计算并在后台运行它们

    @ 2)如何“异步”和“等待”启动它:在这里,在方法返回之前执行并完成了计算,但是由于async/ awaitUI没有被阻塞(
    顺便说一句,此类事件处理程序是的唯一有效用法)async void因为事件处理程序必须返回void- async Task在所有其他情况下使用

    @
    3)Thread现在使用代替新的Task。为了以后能够检查其(成功)完成情况,我们将其保存在全局calcTask成员中。在后台,这还会启动一个新线程并在该线程中运行操作,但是它更易于处理并具有其他一些好处。

    @ 4)在这里我们也开始动作,但是这次我们返回任务,因此“异步事件处理程序”可以“等待它”。我们还可以创建async Task CalcAsync()然后await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);(FYI:ConfigureAwait(false)避免死锁,如果您使用async/
    ,则应仔细阅读此内容,await因为这里将对此进行大量解释),这将导致相同的工作流程,但Task.Run唯一的“等待操作”,这是最后一个操作,我们可以简单地返回任务并保存一个上下文切换,从而节省了一些执行时间。

    @ 5)现在,我在这里使用“强类型通用事件”,以便我们可以轻松地传递和接收“状态对象”

    @
    6)在这里,我使用下面定义的扩展名(除了易于使用之外)解决了旧示例中可能出现的竞争情况。如果此时恰好在另一个线程中删除了事件处理程序,则可能发生了该事件nullif-check之后但在调用之前发生的情况。这不可能在这里发生,因为扩展获取了事件委托的“副本”,并且在相同情况下,处理程序仍在Raise方法内注册。



推荐阅读
知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看