是否可以在没有跨线程异常的情况下将worker和GUI分开?

达斯汀·尼芬格(Dustin Nieffenegger)

我有一个正在处理的项目,该项目具有Windows gui(可选),并且如果没有提供gui,则可以写入gui或控制台的工作程序。gui是可选的,以使该项目与可能没有桌面环境的系统向后兼容(我最终可能最终还是用C或C ++来重新构建该项目,但由于时间限制,我现在需要一些工作)。程序将运行在大多数计算机上(目前)具有Windows XP。(我的目标是.NET Framework 4.0.3)。

由于我希望gui是可选的,因此我不希望worker类位于aBackgroundWorker或a内部Form在我的真实项目中,我有一个UserInterface“接口”(c#接口),可以通过各种用户界面来实现。

在Windows GUI中,tfhere是Form带有按钮的主按钮,可打开对话框Form该对话框有一个多行文本框,工作人员可以在其中附加行。

由于我没有使用aBackgroundWorker或其他常规的处理方式,BeginInvoke因此在创建窗口句柄之前遇到了与交叉线程操作和调用有关的各种问题通过本质上调用_ = MainForm.HandleinMainForm的构造函数以强制在显示窗口之前创建窗口句柄,我能够“解决”窗口句柄问题(以便工作人员可以将行附加到文本框,这可能会在gui之前发生)已经显示过)。

这是我的最小的,可复制的示例,它捕获了我在实际项目中遇到的问题。当以下情况遇到此问题:a)从MainForm构造函数中删除窗口句柄的创建,这会导致BeginInvoke抱怨在创建窗口句柄之前被调用,或者b)像现在这样,一旦对话框窗口关闭并重新打开对它的调用ShowDialog由于跨线程操作而失败。

Program.cs

using System;
using System.Windows.Forms;

namespace MinimalExample
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Gui gui = new Gui();
            Worker worker = new Worker(gui);

            worker.start();
            gui.show();
        }
    }
}

Gui.cs

using System.Windows.Forms;

namespace MinimalExample
{
    class Gui
    {
        private readonly DialogForm _dialog_form;
        private readonly MainForm _main_form;

        public Gui()
        {
            _dialog_form = new DialogForm();
            _main_form = new MainForm(_dialog_form);
        }

        public void addLine(string line)
        {
            _dialog_form.addLine(line);
        }

        public void show()
        {
            Application.Run(_main_form);
        }
    }
}

MainForm.cs

using System;
using System.Windows.Forms;

namespace MinimalExample
{
    public partial class MainForm : Form
    {
        private readonly DialogForm _dialog_form;

        public MainForm(DialogForm form)
        {
            _dialog_form = form;

            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            _dialog_form.ShowDialog(this);
        }
    }
}

DialogForm.cs

using System;
using System.Windows.Forms;

namespace MinimalExample
{
    public partial class DialogForm : Form
    {
        public DialogForm()
        {
            InitializeComponent();

            _ = Handle;
            _ = textBox1.Handle;

            /* Visible = true;
               Visible = false; */
        }

        public void addLine(string line)
        {
            Action action = () =>
            {
                textBox1.AppendText(line);
                textBox1.AppendText(Environment.NewLine);
            };

            if (InvokeRequired)
                textBox1.BeginInvoke(action);
            else
                action();

        }
    }
}

Worker.cs

using System;
using System.Threading;

namespace MinimalExample
{
    internal class Worker
    {
        private readonly Gui _gui;
        private readonly Thread _thread;

        internal Worker(Gui gui)
        {
            _gui = gui;
            _thread = new Thread(new ThreadStart(working));
            _thread.IsBackground = true;
        }

        private void working()
        {
            while (true)
            {
                if (_gui != null)
                    _gui.addLine("Test");
                else
                    Console.WriteLine("Test");
                Thread.Sleep(1000);
            }
        }

        public void start()
        {
            _thread.Start();
        }
    }
}

这是怎么回事 而且,有没有一种方法可以将GuiWorker分开,却没有这些运行时异常?

吉米

线程部分在这里不是问题。您只需要以稍微不同的方式处理表单创建:

首次设置:

  • 窗体的句柄创建可以强制调用CreateHandle()之后InitializeComponent()(该净源代码相关的方法调用更有趣,还要注意调用UpdateHandleWithOwner() )。
  • 显示工作线程输出的下一个条件是,窗体也是可见的,因为调用TextBox句柄时还需要它存在。
  • Gui类需要BeginInvoke()Form的AddLine()方法,因为方法调用是在另一个线程中生成的(Gui对象由Worker对象线程使用)。
  • 我在Gui类中添加了一个公共属性public bool CanWrite::Worker类可以检查此属性以确定应将其输出写入何处。
    公共财产返回:dialogForm != null && dialogForm.Visible;,因为↓:
  • 显示了DialogForm的调用ShowDialog():这意味着当DialogForm关闭时,不处理该Form。同样,该对象在Gui类中仍具有引用。关闭时,其Visible属性返回false

第二种设置:

  • 由于(基于注释)此控制台应用程序应在创建Gui对象时输出到DialogForm表单,并且不一定立即显示该对话框,因此DialogForm中的TextBox控件应在Worker类中缓存该线程发布的文本行。
    这需要一个简单的编辑:更改dialogForm != null && dialogForm.IsVisiblejust dialogForm != null,然后在Gui.AddLine()调用之前验证句柄状态,如果当前没有可用的句柄,则缓存文本行。
  • 所有者窗体MainForm指示DialogForm在DialogForm.ShowDialog()返回时重新创建其句柄由于ShowDialog()用于显示DialogForm,因此不处理该表单。重新创建句柄不会导致子控件丢失其内容。

实施两个选项:

IsVisible属性检查可能会变成Gui的“ Property”,例如bool UpdateOnDialogVisible,以进行测试,CanWrite,以便根据此属性的状态将文本写入TextBox。


经过测试:

-Windows 7(我没有WinXP计算机)
-.Net Framework 4.0
-C#5

在Program.cs中:

class Program
{
    private static Worker worker = null;
    private static Gui gui = null;

    // [...]

    gui = new Gui();
    worker = new Worker(gui);

    worker.start();
    gui.Show();
}

在Gui.cs中

public class Gui
{
    private StringBuilder sb = null;
    // [...]

    public Gui() {
        sb = new StringBuilder();
        dialogForm = new DialogForm();
        mainForm = new MainForm(dialogForm);
    }

    public bool CanWrite {
        get { return dialogForm != null }
        // Or, with the condition that the Dialog is already visible:  
        // get { return dialogForm != null && dialogForm.Visible; }
    }

    public void AddLine(string line) {
        sb.AppendLine(line);
        // Safety measure: cache if the handle is not available at this time
        if (this.CanWrite && dialogForm.IsHandleCreated) {
            dialogForm.BeginInvoke(new MethodInvoker(() => {
                dialogForm.AddLine(sb.ToString());
                sb.Clear();
            }));
        }
    }
    // [...]
}

在Worker.cs中:

internal class Worker
{
    // [...]
    private void working() {
        while (true) {
            if (gui != null && gui.CanWrite) {
                gui.AddLine("Test");
            }
            else {
                Console.WriteLine("Test");
            }
            Thread.Sleep(1000);
        }
    }
    // [...]
}

在DialogForm.cs中:

public partial class DialogForm : Form
{
    private TextBox textBox1;

    public DialogForm() {
        InitializeComponent();
        this.CreateHandle();
    }

    public void AddLine(string line) {
        if (this.IsDisposed || !this.IsHandleCreated) return;
        this.textBox1.AppendText(line);
        this.textBox1.ScrollToCaret();
    }

    public void RecreateWindow() {
        this.CreateHandle();
    }

    private void InitializeComponent() {
        // [...]
    }
}

在MainForm.cs中:

public partial class MainForm : Form
{
    private Button button1;
    internal readonly DialogForm dialogForm = null;

    public MainForm() : this(null) { }
    public MainForm(DialogForm form) {
        dialogForm = form;
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;
        if (dialogForm != null) dialogForm.ShowDialog(this);
        button1.Enabled = true;
        // As describe in the notes, if a, e.g., UpdateOnDialogVisible () property is 
        // created, call this method when this property is true, to show text on this 
        // Window only when is Visible.
        dialogForm.RecreateWindow();
    }

    private void InitializeComponent() {
        // [...]
    }
}

它是这样工作的:

第一选择

  • 仅当在生成输出时指定的表单可见时,控制台输出才会重定向到表单:

控制台和WinForms线程1

第二个选项
创建Gui类时,控制台输出始终定向到指定的表单。

  • 每次关闭都会重新创建窗体的句柄。由于使用ShowDialog()来显示它,因此不会处理Form。
  • 关闭窗体时,用于显示控制台输出的TextBox也可以缓存输出。
  • 如果未在正确的时间创建Window句柄,则StringBuilder对象将用作安全辅助缓存(由于在不同线程中生成了方法调用,因此考虑了假设的竞争条件)。

控制台和WinForms线程2

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

是否可以在没有GUI的情况下运行Selenium(Firefox)Web驱动程序?

可以在没有GUI的情况下运行JProfiler吗?

是否可以在没有 Groovy 的情况下使用 Jenkins

TCPClient是否可以在没有网卡的情况下使用?

是否可以在没有 sudo 的情况下使用 docker?

是否可以在没有 Anaconda 的情况下使用 scrapy?

是否可以在没有ReactJS的情况下使用RelayJS和GraphQL?

是否可以在没有服务器的情况下使用实体类和EntityManager?

Play 2.3.x是否可以在没有激活器(和maven)的情况下使用?

MySQL和Java是否可以在没有文件(即内存)的情况下“加载数据”?

是否可以在没有QApplication的情况下使用QML和QtQuick?

是否可以在没有CDN和Node的情况下运行Babel?

是否可以在没有映射模板的情况下使用AppSync和GraphQL?

是否可以在没有root用户访问权限的情况下安装和运行Google Chrome?

是否可以在没有桌面的情况下安装和使用cool-retro-term?

是否可以在没有Hadoop的情况下将Spark用于开发环境?

是否可以在没有repr(C)的情况下将结构传递给C API?

是否可以在没有枚举的情况下将错误原因存储在结构中?

是否可以在没有JavaScript的情况下将<img>用作完整的背景图像?

是否可以在没有预先定义的情况下将结构变量作为函数参数传递?

是否可以在没有 CCSM 的情况下将程序固定到特定工作区?

在没有C11线程的情况下,C标准库函数是否是线程安全的?

是否可以在没有curl和wget的情况下将数据发布/获取到TLSv1.1 +安全站点?

是否可以在没有 Apple Developer ID 和 iPhone 设备的情况下将 iOS 应用程序发送给客户端进行测试?

我可以在没有推送通知许可的情况下安装 Service Worker 吗?

是否可以在没有循环的情况下将字符串与数组的所有项目进行比较?

是否可以通过规范化并在没有空列的情况下跨多个表使用唯一的外键?

在没有实体框架和SQL Server的情况下,新的ASP.NET MVC身份框架是否可以工作?

是否可以在没有RequireJS的情况下使用最新版本的JQuery,Underscore和Backbone?