如何更新从另一个线程UI另一个类运行 [英] How to update UI from another thread running in another class

查看:101
本文介绍了如何更新从另一个线程UI另一个类运行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在写我的C#的第一个节目,我非常新的语言(仅用于用C工作至今)。我已经做了很多的研究,但所有的答案太笼统,我根本插不上话将不起作用。

I am currently writing my first program on C# and I am extremely new to the language (used to only work with C so far). I have done a lot of research, but all answers were too general and I simply couldn't get it t work.

所以在这里我(很常见)的问题:
我有一个WPF应用程序从用户填写几个文本框的输入参数,然后使用该做很多与他们的计算。他们大概需要2-3分钟,所以我想更新进度条和文本块,告诉我当前的状态是什么。
此外,我需要在UI输入从用户存储和给他们的线程,所以我有一个三等功,我用它来创建一个对象,并希望这个对象传递给后台线程。
很显然,我会跑在另一个线程计算的,所以UI不会冻结,但我不知道如何更新UI,因为所有的计算方法是另一类的一部分。
很多reasearch后,我想一起去将使用调度员和第三方物流,而不是一个BackgroundWorker的最佳方法,但老实说,我不知道他们是如何工作和20小时左右的试错与其他的答案后,我决定问一个问题我自己。

So here my (very common) problem: I have a WPF application which takes inputs from a few textboxes filled by the user and then uses that to do a lot of calculations with them. They should take around 2-3 minutes, so I would like to update a progress bar and a textblock telling me what the current status is. Also I need to store the UI inputs from the user and give them to the thread, so I have a third class, which I use to create an object and would like to pass this object to the background thread. Obviously I would run the calculations in another thread, so the UI doesn't freeze, but I don't know how to update the UI, since all the calculation methods are part of another class. After a lot of reasearch I think the best method to go with would be using dispatchers and TPL and not a backgroundworker, but honestly I am not sure how they work and after around 20 hours of trial and error with other answers, I decided to ask a question myself.

下面我的程序的结构非常简单:

Here a very simple structure of my program:

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);
        }
    }
}

如果有人有一个简单的解释如何从里面TestMethod的更新UI,我将非常感激。由于我是新的C#和面向对象编程,太复杂的答案,我将很可能不明白,我会尽我所能,但。

I would be very grateful if someone had a simple explanation how to update the UI from inside the testmethod. Since I am new to C# and object oriented programming, too complicated answers I will very likely not understand, I'll do my best though.

此外,如果有人(可能使用的BackgroundWorker或其他任何东西)我愿意看到它在一般一个更好的主意。

Also if someone has a better idea in general (maybe using backgroundworker or anything else) I am open to see it.

推荐答案

首先,你需要使用 Dispatcher.Invoke 来从另一个线程更改UI要做到这一点,从另一个类,您可以使用事件。结果
然后,你可以注册在主类事件(S)和调度更改用户界面,并在计算类你扔的事件时要通知用户界面:

First you need to use Dispatcher.Invoke to change the UI from another thread and to do that from another class, you can use events.
Then you can register to that event(s) in the main class and Dispatch the changes to the UI and in the calculation class you throw the event when you want to notify the UI:

class MainWindow
{
    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) - 这是长一点,因为我会展示一些不同的可能性:

UPDATE:
As it seems this is still an often visited question and answer I want to update this answer with how I would do it now (with .NET 4.5) - this is a little longer as I will show some different possibilities:

class MainWindow
{
    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)如何启动synchonuos计算,并在后台运行它们。

@1) How to start the "synchonuos" calculations and run them in the background

@ 2)如何启动asynchronuos和等待它:这里的计算是执行和完成方法返回之前,但由于异步 / 的await UI不是堵塞( BTW:这样的事件处理程序的异步无效,唯一有效的用法作为事件处理程序必须返回无效 - 在所有其他情况下使用异步任务 的)

@2) How to start it "asynchronuos" and "await it": Here the calculation is executed and completed before the method returns, but because of the async/await the UI is not blocked (BTW: such event handlers are the only valid usages of async void as the event handler must return void - use async Task in all other cases)

@ 3)取而代之的是新的发的我们现在使用工作。为了以后能够检查其(全成)完成,我们将其保存在全局 calcTask 成员。在后台,这也启动一个新的线程并运行动作存在,但它更容易处理并具有一些其它的好处。

@3) Instead of a new Thread we now use a Task. To later be able to check its (successfull) completion we save it in the global calcTask member. In the background this also starts a new thread and runs the action there, but it is much easier to handle and has some other benefits.

@ 4)在这里我们也开始行动,但这次我们回到了任务,所以异步事件处理程序,可以等待着它。我们也可以创建异步任务CalcAsync()然后等待Task.Run(()=&GT; calc.TestMethod(输入))。ConfigureAwait( FALSE); (FYI:在 ConfigureAwait(假)为避免死锁,应该在这读了,如果你使用异步 / 等待,因为这将是很多在这里解释),这将导致相同的工作流程,但作为 Task.Run 是唯一的awaitable操作,并且是最后一个,我们可以简单地返回任务,救一个上下文切换,这样可以节省一些执行时间。

@4) Here we also start the action, but this time we return the task, so the "async event handler" can "await it". We could also create async Task CalcAsync() and then await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false); (FYI: the ConfigureAwait(false) is to avoid deadlocks, you should read up on this if you use async/await as it would be to much to explain here) which would result in the same workflow, but as the Task.Run is the only "awaitable operation" and is the last one we can simply return the task and save one context switch, which saves some execution time.

@ 5)在这里,我现在用一个强类型的一般事件,所以我们可以传递和接收我们的状态对象易

@5) Here I now use a "strongly typed generic event" so we can pass and receive our "status object" easily

@ 6)这里我用下面定义的扩展,它(除了易用性)解决可能的竞争条件在旧的例子。在那里,它可能发生该事件得到了如果 -check,但在此之前调用,如果事件处理程序在刚才那一瞬间在另一个线程中被删除。这不可能发生在这里,作为扩展得到一个事件委托的复制,并在相同的情况下处理器是注册的提高方法内。

@6) Here I use the extension defined below, which (aside from ease of use) solve the possible race condition in the old example. There it could have happened that the event got null after the if-check, but before the call if the event handler was removed in another thread at just that moment. This can't happen here, as the extensions gets a "copy" of the event delegate and in the same situation the handler is still registered inside the Raise method.

这篇关于如何更新从另一个线程UI另一个类运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆