【问题标题】:Call methods in WPF window from main thread从主线程调用 WPF 窗口中的方法
【发布时间】:2013-12-16 19:45:08
【问题描述】:

用例
我正在用 C# 开发一个小应用程序,它被另一个应用程序调用以从 Internet 检索数据。它作为一个独立的进程运行,但几乎所有与它的交互都由调用应用程序管理。因此它没有图形用户界面。但是,我想使用 WPF 添加一个进度条,该进度条在某些可能需要一分钟的数据检索期间显示。估计完成了多少工作以及剩下多少工作相当容易,因此我找到了一个合适的进度条。

研究完成
在阅读了 Albahari 的关于线程的 pdf 的大部分内容 (http://www.albahari.info/threading/threading.pdf) 之后,我对线程有了相当的了解。在这件事上,我还阅读了很多关于 SO 和 MSDN 的帖子。大多数帖子建议使用后台工作者进行耗时的数据检索,同时将 GUI 保留在主线程中,因此建议使用后台工作者的解决方案。但是在这种情况下感觉很尴尬,主要任务是数据检索而不是 GUI 交互。

我花了很多时间试图理解不同的教程和论坛帖子,同时试图使它们符合我的问题,但我没有成功,现在我几乎回到了原点。基本上我想最终得到以下两个类:

进度条窗口

public partial class ProgressBarWindow : Window
{
    public ProgressBarWindow()
    {
        InitializeComponent();
    }

    public void setValue(int value) 
    {
        // This function should be available from the main thread
    }
}

查询器

Public class Querier
{



    public List<Item> getItems()
    {
        // call ProgressBarWindow.setValue(0);
        ...
        // call ProgressBarWindow.setValue(100);
        // call ProgressBarWindow.Close();
    }
}

我的理解是 UI 必须在单线程下运行,因此我的 ProgressBarWindow 对象无法在新线程中实例化,同时可用于主线程(有点)。

Dispatcher.BeginInvoke 似乎是我的救星,但到目前为止,我还无法弄清楚 Querier 类中应该包含哪些内容以及 ProgressBarWindow 类中应该包含哪些内容。如何让两个线程与同一个 ProgressBarWindow 实例交互?

请询问您是否需要更多详细信息,我会尽力澄清。

【问题讨论】:

  • 请避免使用setXXX()getXXX() 语法。它看起来很像java,我快要吐了。取而代之的是,使用类似 C# 的代码并创建适当的属性。
  • 点了!我通常会尝试。

标签: c# wpf multithreading


【解决方案1】:

您可以使用Progress 类以长时间运行操作的当前进度更新 UI。

首先在你的 UI 中创建一个Progress 的实例:

Progress<int> progress = new Progress<int>(currentProgress =>
{
    progressBar.Value = currentProgress;
    //todo do other stuff
});

然后将其传递给长时间运行的进程:

public List<Item> getItems(IProgress<int> progress)
{
    progress.Report(0);
    //todo do something
    progress.Report(100);
}

【讨论】:

  • 我还没有尝试过,但我正在尝试在脑海中简要地解决它。我看不到应该在哪里创建新线程。这会在“长期运行的过程”中实施吗?
  • @Anders 我的印象是您在后台线程中运行某些代码没有问题,而是将进度返回给 UI 线程。您可以简单地使用 Task.Run(() =&gt; whatever.GetItems(progress)) 在非 UI 线程中运行 getItems,尽管还有许多其他技术都可以正常工作。这是使用Progress 的主要优势,它不关心您最终如何在另一个线程中运行代码。
  • 我希望反过来。进度条应该在后台线程中运行,这给我留下了类似的信息:Task.Run(() =&gt; new ProgressBarDialog().showDialog()。但我想在匿名函数内部也可以交易代表,以便通过它们传输状态变化?
  • @Anders 这将创建多个 UI 线程。不要那样做。只是不要。拥有一个管理所有 UI 组件的 UI 线程。如果您长期运行非 UI 工作,请在非 UI 线程中执行此操作。尝试创建第二个 UI 线程来管理进度,以便您可以在 UI 线程中执行非 UI 工作,您将为自己创造更多的麻烦。
  • 我实际上没有其他用户界面。或者至少不是我所说的 UI。因此,程序与用户“交互”的唯一时间是 ProgressBarWindow。没有实现其他 Winform 或 WPF。
【解决方案2】:

这是我通常使用的通用函数:

    public static void Invoke(this UIElement element,Action action)
    {
        element.Dispatcher.Invoke(action, null);
    }

要使用它,只需调用:

this.Invoke(() => ProgressBarWindow.SetValue(0));

因此,在 getItems() 函数中,您将有以下内容:

public List<Item> getItems()
{
    ProgressBarWindow wnd;
    MainWindow.Invoke(() => wnd = new ProgressBarWindow())
    MainWindow.Invoke(() => wnd.SetValue(0))
    ...
    MainWindow.Invoke(() => wnd.SetValue(100))
    MainWindow.Invoke(() => wnd.Close())
}

确保您始终可以通过任何方式访问主窗口(从 App.xml 或 App.Run(...) 运行的窗口。然后您可以通过它发出任何 GUI 操作(即使例如,您必须创建一个新的加载程序窗口,只要它在主线程中完成)

【讨论】:

  • 感谢您的回答。但是我不确定如何处理 Invoke 方法中的第一个形参。您的示例没有说明要发送什么作为实际参数,并且 Visual Studio 抱怨参数的数量。
【解决方案3】:

App.xaml

public partial class App : Application
{
    private void Application_Startup_1(object sender, StartupEventArgs e)
    {
        Task.Factory.StartNew<List<int>>(() => Querier.GetItems());
    }
}

ProgressBarWindow.xaml.cs

public partial class ProgressWindow : Window
{
    public ProgressWindow()
    {
        InitializeComponent();
        Querier.Start +=()=> Visibility = Visibility.Visible;
        Querier.Stop += () => Visibility = Visibility.Collapsed;
        Querier.ReportProgress +=OnReportProgress;
    }

    public void OnReportProgress(int value)
    {
        txtBox.Text = value.ToString();
    }
}

ProgressBarWindow.xaml

<Grid>
    <TextBox x:Name="txtBox"></TextBox>
</Grid>

查询者

public class Querier
{
    public static event Action Start;

    public static event Action Stop;

    public static event Action<int> ReportProgress;

    public static List<int> GetItems()
    {
        if (Start != null)
            App.Current.Dispatcher.BeginInvoke(Start,null);

        for (int index = 0; index <= 10; index++)
        {
            Thread.Sleep(200);
            if (ReportProgress != null)
                App.Current.Dispatcher.BeginInvoke(ReportProgress, index*10);
        }


        if (Stop != null)
            App.Current.Dispatcher.BeginInvoke(Stop, null);

        return Enumerable.Range(1, 100).ToList();

    }

}

我只是想给出一个想法希望这会有所帮助。

【讨论】:

  • 我的项目并不是真正的 WPF 项目,所以我缺少一个 app.xaml。我刚刚包含了一个 WPF 窗口。因此,我有一个 Main 方法作为入口点。这似乎让我无法拨打App.Current.... 或同等电话。
  • 是的,这是正确的。我目前有一个名为ThreadTest 的类,其中有一个main 以及声明的[STAThread] 属性。 Visual Studio 无法识别 Current 上的 ThreadTest
  • 是控制台应用还是窗口应用?
  • 这是我正在测试的控制台应用程序。这有关系,所以我必须更改为 Windows 应用程序吗?
猜你喜欢
  • 2018-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多