【问题标题】:Add DataRow to DataGridView from several Threads从多个线程将 DataRow 添加到 DataGridView
【发布时间】:2014-06-30 01:15:48
【问题描述】:

我遇到了一个我不知道如何解决的问题。

我创建了一个包含 3 个表单的应用程序。 frm1 是完成所有繁重工作的主要形式。 frm2 只是一个设置表单。 frm3 是一个状态窗口,它可能会显示从 frm1 和 frm2 收到的一些日志记录。

在启动时,两种形式(frm2 和 frm3)都将被初始化,但未显示。 要显示其中一种表单,您需要单独打开它(通知图标)。

每个表单(frm2 和 frm3)都作为 STA 线程运行,因为主表单上的负载会很重,有时会导致表单显示延迟很大。

当我在没有先启动设置的情况下运行 frm3(包含 DataGridView)时,一切都很好。 frm3 不断记录来自 frm2 和 frm1 所需的所有信息。 即使我只运行frm2,它也会一直登录到DataGridView。 但是首先启动frm2(它开始从frm3登录到DataGridView)然后运行frm3会导致InvalidOperationException,因为frm2拥有从frm3访问DataGridView的权限。 我真的无法通过 Invoke 传递数据,因为这两种形式(frm2 和 frm3)大多都没有显示。

有什么办法解决这个问题吗?

以下是我的代码中的一些 sn-ps,您可以看看:

frm1 - 主要

public partial class Tray : Form
{
    private Settings s = new Settings();
    public static LogWindow log = new LogWindow();

    public Tray()
    {
        InitializeComponent();

        Data.ni_tray = ni_tray;

        ctm.MenuItems.Add("Start", StartStop);
        ctm.MenuItems.Add("Einstellungen", OpenSettings);
        ctm.MenuItems.Add("Log", OpenLog);
        ctm.MenuItems.Add("Exit", CloseApp);
        ni_tray.ContextMenu = ctm;
    }

    private void OpenSettings(object sender, EventArgs e)
    {
        if (!s.Visible)
        {
            Thread thread = new Thread(() => s.ShowDialog());
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }
        else
            s.Invoke(new MethodInvoker(() => { s.locate(); }));
    }
    private void OpenLog(object sender, EventArgs e)
    {
        if (!log.Visible)
        {
            Thread thread = new Thread(() => log.ShowDialog());
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }
        else
            log.Invoke(new MethodInvoker(() => { log.locate(); }));
    }

[ ... }
}

frm2 - 设置

public partial class Settings : Form
{
    private readonly string[] info = { String.Empty, "Füge Programm hinzu ...", "Füge Szenario hinzu ...", "Sichere Konfiguration ...", "Übernehme Änderungen ohne Sicherung ...", "Verwerfe Konfiguration ...", "Konfiguration erfolgreich geladen ...", "Konfiguration erfolgreich gespeichert ..." };
[ ... ]

    private void WriteConf()
    {
        writeSection(conf.Root.Element("Applications"), dgv_apps);
        writeSection(conf.Root.Element("Scenarios"), dgv_scen);
        writeSection(conf.Root.Element("Misc"));
        conf.Save(ConfFile);
        Tray.log.writeLogEntry("Settings", info[7]);
        showInfo(info[7]);
    }

[ ... ]
}

frm3 - 日志窗口

public partial class LogWindow : Form
{
    public LogWindow()
    {
        InitializeComponent();
    }

    private void btn_close_Click(object sender, EventArgs e)
    {
        this.Hide();
    }

private void dgv_log_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
    {
        btn_clear.Enabled = true;
        dgv_log.ClearSelection();
        dgv_log.FirstDisplayedScrollingRowIndex = dgv_log.Rows.Count - 1;
        dgv_log.Rows[dgv_log.Rows.Count - 1].Selected = true;
    }

public void writeLogEntry(string application, string info)
    {
        if (dgv_log.InvokeRequired)
            this.Invoke((MethodInvoker)delegate { dgv_log.Rows.Add(DateTime.Now.ToShortTimeString(), application, info); });
        else
            dgv_log.Rows.Add(DateTime.Now.ToShortTimeString(), application, info);
    }

    private void btn_clear_Click(object sender, EventArgs e)
    {
        dgv_log.Rows.Clear();
        btn_clear.Enabled = false;
    }
}

这个会导致异常:

if (!log.Visible)
{
    Thread thread = new Thread(() => log.ShowDialog());
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
}

【问题讨论】:

  • 我第一次听说ApartmentState,但是.NET Framework 不使用套间,托管对象自己负责以线程安全的方式使用所有共享资源。你确定你知道你在做什么?另一件事是您需要在执行Invoke/BeginInvoke之前检查控制IsHandleCreated
  • 好吧,我是 Winforms 的新手。主要只开发 dll 和控制台应用程序。即使我从普通线程运行它:它仍然是一样的
  • 不。然后就变得不一样了,因为你必须在 UI 线程中进行 UI 操作(而且你必须只有一个 UI 线程)。您可以尝试使用 host(大多数父表单)来处理尚未创建(无处理)表单的调用。
  • 所以我不能将数据传递到 logviews 线程上的网格,对吧?我不确定如何访问主 UI 线程。

标签: c# multithreading winforms datagridview


【解决方案1】:

可以有多个线程,但请只使用一个 UI 线程。反正有主窗口,所以所有窗口都可以使用它的实例来调用。

基本上:

public partial class FormTray : Form
{
    private static _instance;
    public static Instance { get { return _instance; } } // to get from anywhere

    public FormTray()
    {
        InitializeComponents();
        _instance = this; // store instance
    }

    private void OpenSettings(object sender, EventArgs e)
    {
        FormSettings.Show(); // call static method 
    }

    private void OpenLog(object sender, EventArgs e)
    {
        FormLog.Show(); // call static method
    }

    // ...
}

public partial class FormSettings
{
    private static FormSettings _instance; // to be used from static methods

    public FormSettings()
    {
        InitializeComponents();
        _instance = this;
    }

    public static Show()
    {
        if(_instance == null) // not yet created - create and show
        {
            _instance = new FormSettings();
            _instance.Show();
        }
        else
            _instance.Visible = true; // was created and hidden - un-hide
    }

    void FormSettings_Closing(object sender, FormClosingEventArgs e)
    {
        e.Cancel = true; // disable closing
        Visible = false; // hide instead
    }

    // ...
}

public partial class FormLog
{
    // ... same as settings

    // static method for log messages
    public static AddMessage(string message)
    {
        if(FormTray.Instance != null && FormTray.Instance.IsHandleCreated) // avoid errors if attempting to log before main form is created
        {
             if(FormTray.Instance.InvokeRequired)
                 FormTray.Instance.BeginInvoke(() = { AddMessage(message); } // need invoke
             else
             {
                 // ... logging here
             }
        }
    }
}

这不是一个完整的解决方案,只是想法。

  • 命名。
  • 使用Visible=true/false 代替表单打开/关闭。
  • 每种表单的单例类型。
  • 通过主窗体实例调用日志消息。
  • 线程安全FormLog.AddMessage()

【讨论】:

  • 测试了你的建议,它有效!不得不修改代码,因为您不能在静态方法上调用实例。没关系,我知道单 UI 线程是如何工作的。感谢您的帮助:)
猜你喜欢
  • 2011-11-03
  • 2012-11-25
  • 2017-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多