提供表单的多个实例,但一次处理一个事件

时间:2009-12-07 23:39:02

标签: c# multithreading delegates mutex

我需要能够让同一个表单的多个实例打开,因为我的应用程序可以同时在不同的地方使用。另一方面,我需要能够在“OK”事件期间一次处理一个操作,以确保数据安全存储而不会被其他表单实例意外覆盖。

我使用.Show()方法显示我的表单,因为我在其中使用了几个代表:

        private void newToolStripMenuItem_Click(object sender, EventArgs e)
    {
        bookingForm = new BookingForm(AddMemberBooking, AddUserBooking, CloseBooking);
        bookingForm.Show();
    }

我曾尝试使用互斥锁,一次只能按下一个OK按钮事件,我已经将它与一个线程相结合,以满足我需要的标准。

当我点击“确定”按钮时,我收到以下错误:

  

跨线程操作无效:控制'comboBoxDay'从其创建的线程以外的线程访问。

这是我的预订表格类的代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace Collection
{
    //Allows the class to be serialized
    [Serializable()]

    public delegate void AddMemberBookingMethod(int date, int time, int mNo);
    public delegate void AddUserBookingMethod(int date, int time, string fName, string lName, string pCode);
    public delegate void CloseBookingFormMethod();


    public partial class BookingForm : Form
    {
        public CloseBookingFormMethod CloseBookingForm;
        public AddMemberBookingMethod AddMemberBooking;
        public AddUserBookingMethod AddUserBooking;
        private Mutex bookingMut = new Mutex();
        private Thread thread;

        public bool IsUser;

        public BookingForm(AddMemberBookingMethod ambm, AddUserBookingMethod aubm, CloseBookingFormMethod cbfm)
        {
            InitializeComponent();
            AddMemberBooking = ambm;
            AddUserBooking = aubm;
            CloseBookingForm = cbfm;

            checkBoxMember.Checked = true;
            //Control.CheckForIllegalCrossThreadCalls = false;
        }

        private void checkBoxUser_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBoxUser.Checked)
            {
                IsUser = true;
                checkBoxMember.CheckState = CheckState.Unchecked;
                textBoxMNo.Enabled = false;
                textBoxFName.Enabled = true;
                textBoxLName.Enabled = true;
                textBoxPCode.Enabled = true;
            }
            else
            {
                IsUser = false;
                checkBoxMember.CheckState = CheckState.Checked;
                textBoxMNo.Enabled = true;
                textBoxFName.Enabled = false;
                textBoxLName.Enabled = false;
                textBoxPCode.Enabled = false;
            }

        }

        private void checkBoxMember_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBoxMember.Checked)
            {
                IsUser = false;
                checkBoxUser.CheckState = CheckState.Unchecked;
                textBoxFName.Enabled = false;
                textBoxLName.Enabled = false;
                textBoxPCode.Enabled = false;
            }
            else
            {
                IsUser = true;
                checkBoxUser.CheckState = CheckState.Checked;
                textBoxMNo.Enabled = false;
                textBoxFName.Enabled = true;
                textBoxLName.Enabled = true;
                textBoxPCode.Enabled = true;
            }

        }

        private void buttonOK_Click(object sender, EventArgs e)
        {
            this.thread = new Thread(new ThreadStart(MakeBooking));
            this.thread.Name = "bookingThread";
            this.thread.Start();
        }

        private void MakeBooking()
        {
            this.bookingMut.WaitOne();

            int date = this.comboBoxDay.SelectedIndex;
            int time = this.comboBoxTime.SelectedIndex;

            if (IsUser)
            {
                string fName = textBoxFName.Text;
                string lName = textBoxLName.Text;
                string pCode = textBoxPCode.Text;

                AddUserBooking(date, time, fName, lName, pCode);
            }
            else
            {
                int mNo = int.Parse(textBoxMNo.Text);

                AddMemberBooking(date, time, mNo);
            }

            this.bookingMut.ReleaseMutex();

            CloseBookingForm();
        }

        private void buttonClose_Click(object sender, EventArgs e)
        {
            CloseBookingForm();
        }
    }
}

我意识到我可能不是以最有效的方式做到这一点,但时间是一个因素。 我已经研究过这个错误,听说过使用过代理和.Invoke(),但我还是不确定如何解决它。

编辑:

我在搜索问题的修复程序时找到了此代码段。我不明白我将在何处/如何使用它。

if(this.InvokeRequired)
{
    this.Invoke(new MyEventHandler(this.CreateAForm()));
    return;
}

EDIT2:

似乎这家伙终于看到了感觉,通过使用new字创建from,它显然已经通过了标准。我希望在尝试重新发明轮子之前我已经知道了这一点。

3 个答案:

答案 0 :(得分:0)

我认为您可以简单地禁用其他打开表单上的“确定”按钮,为用户提供可视提示。那你甚至不应该有这个问题。为应用程序控制器中的某些内容提供回调委托,该代理控制器知道哪些表单是打开的。每个表单都可以提供一个公共方法来禁用“确定”按钮。禁用所有其他表单上的“确定”按钮。

我并没有真正关注你的代码。我认为互斥体可能首先在表单代码之外(即在执行实际工作的委托中),如果它在单个应用程序中,您可以只使用lock(object)方法来确保一个线程正在执行一段给定的代码。

我还想补充一点,互斥锁不会阻止不同机器上的多个用户能够同时单击“确定”。我不确定这是不是你在不同地方运行的表格在你的问题中的意思。

我认为AddUserBooking和另一个委托应该负责确保它们是线程安全的,这不应该是UI的一部分。如果它们不是线程安全的,为什么不呢?使数据库提交函数在操作期间每个都有自己的数据库连接并且线程安全不应成为问题,这是相对容易的。

答案 1 :(得分:0)

一种简单的方法是使用接受对象的Thread.Start方法的重载:Thread.Start Method (Object)。在此对象中,您将存储所有必要的数据/状态以进行更新。

引用表单及其控件的所有代码都需要移动到OK单击事件方法中,或者重构为只返回数据对象的方法。然后将此对象传递给线程启动方法。

一些伪代码:

on_click_event()
{
   object data=getFormData();
   thread.start(data);
}

有更好的方法可以做到这一点,但这是对您的代码的快速修复。

答案 2 :(得分:0)

您正在获取此异常,因为您的线程正在访问控件。这不合法,只能从UI线程访问控件属性。你对TextBox.Text属性没问题,那个恰好是缓存的。但不是ComboBox.SelectedIndex。从另一个线程关闭表单也会炸弹。

你的互斥锁与它无关,但如果你想防止线程重叠,请保留它。使用委托的Invoke方法不会解决它,它也只是启动一个线程。您需要在一个小帮助器类中收集线程所需的信息,并将其作为参数传递给Thread.Start()方法。

关闭表单也有点棘手,用户可能已经在线程运行时关闭了它。这将导致ObjectDisposed异常。快速解决方法是将表单的Enabled属性设置为false,以便用户无法关闭它。您需要使用表单的Invoke()方法来确保在UI线程上完成关闭。

最后但并非最不重要的是,如果这些线程不花费大量时间(大约一秒钟),请考虑根本不使用线程,而是显示等待光标。