1

背景:

我有一个包含多个操作的类,这些操作需要几秒钟才能完成。与此同时,我想更新 UI。所以通常 BackgroundWorker 是要走的路。但由于某种原因,BackGroundWorker 并不总是按我想要的方式工作(例如:当我尝试使用带有事件的 WebBrowser 并调用 ReportProgress 事件时,BackGroundWorker 似乎崩溃了)。

所以我通过将 Ui 与主线程分开来避免所有这些。

这个伪代码更好地解释了它:

public Ui ui;

main
{
    Thread threadUi = new Thread(initiateUi);      
    //Initiate and start Thread

    //Everything I will do from here on will not have any consequences 
    //on my ui.
    //
    //Object Ui can still be publicly accessed, making it possible to 
    //update the user interface.
}

现在,当我有一个 Bar 类的实例时,我会让 UI 可以像这样访问它:

public Bar bar1;
public Bar bar2;

main
{
    //
    //other stuff here
    //

    Thread threadBar1 = //New Thread where I call the Bar initializer function
                        //and pass bar1 as parameter.
    Thread threadBar2 = //idem dito, except with bar2 as parameter

    //
    //other stuff here
    //
}

通过这种设计,我可以使用以下函数从我的用户界面调用 bar1 和 bar2:

Program.bar1.someFunction();

问题:

现在假设我有一个名为 FooHandler 的类。此类具有在某个 FooDepository 中搜索 Foo 的所有实例的函数以及用于操作 Foo 对象的其他函数。这是一个静态类,因为在我的例子中,它不需要有多个实例。

但是如果我要从 FooHandler 调用一个函数,该函数会在我的 UI 线程中运行,因为那是调用线程(我不太确定,但我找不到任何关于这个主题的文档)。因此,我很有可能即将面临我开始时遇到的问题。

问题:

是否可以在不使用调用线程的处理能力的情况下访问静态类的函数?

4

2 回答 2

3

首先:方法范围(定义它的地方)与程序流程无关。定义方法的位置(FooHandler、BarProvider 或 ThreadX)不影响调用它的位置。实际上方法总是在调用者的线程中调用。

因为您没有提及任何模型、视图或视图模型,并且标题中写着“c#”,所以我假设您在谈论 WinForms。

在 WinForms 中,UI 控件需要从用于创建它们的线程(通常是主线程)中调用(更新)。所有的 UI 控件都实现了 ISynchronizeInvoke 接口,这就是为了做到这一点。所以,而不是常规:

progress.Position = 7;

你需要打电话Invoke

progress.Invoke(new Action(() => progress.Position = 7), null)

由于有很多样板代码,我为自己编写了一个小扩展函数:

public static class ControlExtensions
{
    public static void Synchronize(this Control control, Action action)
    {
        if (control == null || !control.InvokeRequired)
        {
            action();
        }
        else
        {
            control.Invoke(action, null);
        }
    }
}

所以现在你可以:

progress.Synchronize(() => progress.Position = 7);

(打字少一点,更容易阅读)

从技术上讲,在 ISynchronizeTarget 上调用并没有真正调用给定的操作。它只是将一条消息(旧的 WM_xxxx)放入消息队列中(但在调用者的线程中执行此操作),并以委托作为参数。然后,如果目标(控制)线程正在处理消息(在它自己的线程中),它会获取此 WM_xxxx 消息,调用委托(在调用者线程中 - 但这次是 UI 线程)并返回。

如果您需要新的线程来调用 FooHandler,并且您不想等待使用任务(这可能是最简单的方法):

Task.Factory.StartNew(() => FooHandler.SearchOrWhatever(...));

它不会等待(不会阻塞 UI 线程)。

尽管说了这么多,但不要以为它已经完成了。多线程很难。所有支持的构造都可以节省您的打字时间,但困难的部分仍然存在:死锁、竞争条件、饥饿等。

于 2013-06-28T08:14:08.250 回答
2

可以通过使用另一个线程调用此函数。如果您使用 .NET 4,请查看 Task 对象,这将轻松解决问题。例如,如果您的函数返回字符串,那么您需要Task<string>它来调用您的函数。然后根据您的逻辑,您将阻塞直到完成或执行类似的操作。如果您使用的是 .NET 4.5,那么使用 async/await 会更容易。

于 2013-06-28T07:39:28.957 回答