所以我对多线程很新,我参加了一些youtube课程,阅读并且只是制作试用应用程序。WinForms中MultitThreading与监视器,不一致的顺序
我想这个'问题'是一个很好的例子(最后,在一些很好的答案之后),如何处理刚刚进入多线程的人的多线程。我会更新这篇文章,以便为更多读者提供一个很好的参考。
更新1:改变了锁定的方式,我认为Monitor
是'更好',但这取决于你想要什么。代码根据@hatchat的回答进行编辑。我的第一条路仍然可以在LoadAllData()
看到,而新的方式是在SaveData()
,还更新了问题的答案和更多的猜测。
所以我做了一些测试,我想有一个对象,明显地命名为Databases
来执行数据库命令。我的目标是在没有用户看到任何性能问题的情况下继续工作的同时保存数据。一些要求:
- 我必须能够发送数据集到需要在数据库中更新的类。
- 我需要能够从数据库检索数据并将其作为数据集取回。
- 我必须能够调用与
customerId
关联的不同表格。
所以我有2种形式,一种接收所有数据和一种形式,只显示一个表(当客户从form1中选择时)。如果您不介意,我只是向您展示所有代码,在Form1中最重要的按钮是btnLoadAll
,而在表格2中,最重要的代码位于btnSave
事件中。
Form1中:
public delegate void ReturnDelegateDataSet(DataSet data); //used to return data to the main thread, used in form1 and form2
public partial class MultiThread_w_database : Form
{
DataSet allCustomersData;
private int currentSelectedCustomer = -1;
public MultiThread_w_database()
{
InitializeComponent();
btnLoadSelected.Enabled = false;
allCustomersData = new DataSet();
}
private void btnLoadAll_Click(object sender, EventArgs e)
{//normally this would happen on formLoad
//we want to return a dataset, so we have to use a delegate.
ReturnDelegateDataSet delegateReturn = new ReturnDelegateDataSet(returnMethod1);
ThreadDatabase data = new ThreadDatabase(delegateReturn);
listBox1.Items.Add("creating Thread.." + DateTime.Now.ToString("hh:mm:ss:ms"));
Thread loadThread = new Thread(new ThreadStart(data.LoadAllData));
listBox1.Items.Add("Starting Thread.." + DateTime.Now.ToString("hh:mm:ss:ms"));
loadThread.Start();
listBox1.Items.Add("Thread running, waiting for join.." + DateTime.Now.ToString("hh:mm:ss:ms"));
loadThread.Join(60000);//so we are sure it should take longer then 5 seconds
//now .Join still makes the app freeze, but we try to make sure that we don't load data
//if we are still saving data.
if(allCustomersData != null)
{
dgvCustomers.DataSource = allCustomersData.Tables["allCustomers"];
dgvSecondTable.DataSource = allCustomersData.Tables["secondTable"];
listBox1.Items.Add("thread is back and datasource is set" + DateTime.Now.ToString("hh:mm:ss:ms"));
}
listBox1.Items.Add("end of buttonEvent");
}
//method is used to return the dataset
public void returnMethod1(DataSet data)
{
allCustomersData = data;
}
有2种以上的方法,用于检查被选择的内容的行,因此什么CustomerID和一个用于打开穿过所述客户ID的其它形式(如果不是-1)
现在我的窗体2:
public partial class MultiThread_w_Database2 : Form
{
DataSet currentCustomer;
private int currentSelectedCustomer = -1;
//I have this as global so I could recieve the reportString from that object
//we can't do console.writeline in here and you can't access form controls from
//another thread, not easy at least.
ThreadDatabase dataSave;
public MultiThread_w_Database2(int customerId)
{
InitializeComponent();
currentSelectedCustomer = customerId;
//just giving datasource to datagridview, same like before.
//Like I said, global so I could receive the report strings from every action
dataSave = new ThreadDatabase(currentCustomer, currentSelectedCustomer);
}
private void btnSave_Click(object sender, EventArgs e)
{ //magic happening here, btw for some good reason it really saves the data
//even if we give the dataset already in the constructor of this form :)
if(currentSelectedCustomer != -1 && currentCustomer != null)
{
Thread saveThread = new Thread(new ThreadStart(dataSave.SaveData));
saveThread.Start();
}
}
有2种方法,一个定时器,显示记录和一个回调方法,只能在形式的开机使用。
现在的数据库类,最重要的部分是LoadAllData()
,LoadCertainData()
和SaveData()
:
static object locker = new object();
private DataSet currentData;
private SqlDataAdapter adapt;
private int customerId = -1; //if none, -1, might be useful for testing before calling function
private string connectionString = ConfigurationManager.ConnectionStrings["somestring"].ConnectionString;
private ReturnDelegateDataSet callbackData;
private string reportString = ""; //using for reporting back
//CONSTRUCTORS
//constructor for multiThread load ALL data
public ThreadDatabase(ReturnDelegateDataSet callback)
{
currentData = new DataSet();
adapt = new SqlDataAdapter();
callbackData = callback;
}
//constructor for getting data associated to a customer.
public ThreadDatabase(int customerId, ReturnDelegateDataSet callback)
{
currentData = new DataSet();
adapt = new SqlDataAdapter();
this.customerId = customerId;
callbackData = callback;
}
//constructor for multithread Send Data
public ThreadDatabase(DataSet dataNededToUpload, int customerId)
{
currentData = dataNededToUpload;
adapt = new SqlDataAdapter();
}
//here is a property for returning reportString
//METHODS
//just load all data
public void LoadAllData() //must be void, since we will return dataset via delegate
{
Monitor.Enter(locker);
try
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
//doo some stuff to get data, fill in currentData Dataset, 2 tables.
}
}
catch (Exception e) { throw e;}
finally
{
Monitor.Exit(locker);
}
if(callbackData !=null) //'return' dataset
{
callbackData(currentData);
}
}
//load data for one customer
public void LoadCertainData()
{
if (Monitor.TryEnter(locker, 10000)) //with timeout
{
try
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
currentData.Tables.Clear(); //here we load another table, assosiated with the customer
SqlCommand comm = new SqlCommand("SELECT * FROM SecondTable WHERE customerId = @0", conn);
comm.Parameters.AddWithValue("@0", customerId);
currentData.Tables.Add("OneCustomers");
adapt.SelectCommand = comm;
adapt.Fill(currentData.Tables["OneCustomers"]);
}
}
finally
{
Monitor.Exit(locker);
}
if (callbackData != null) //'return' dataset
{
callbackData(currentData);
}
}
else
{
//do other stuff
}
}
//save edited data
public void SaveData()//use dataset and customerId from constructor.
{
reportString += Thread.CurrentThread.Name + "trying to Aquire Lock " + DateTime.Now.ToString("hh:mm:ss:ms") + "\n";
lock (locker)
{
reportString += Thread.CurrentThread.Name + "lock aquired, updateing database" + DateTime.Now.ToString("hh:mm:ss:ms") + "\n";
Thread.Sleep(5000);
using (SqlConnection connect = new SqlConnection(connectionString))
{
connect.Open();
SqlCommandBuilder commbuilder = new SqlCommandBuilder(adapt);
adapt.SelectCommand = new SqlCommand("SELECT * FROM SecondTable WHERE customerId = " + customerId, connect);
adapt.Update(currentData.Tables["OneCustomers"]);
}
reportString += Thread.CurrentThread.Name + "closing lock" + DateTime.Now.ToString("hh:mm:ss:ms") + "\n";
}
}
测试结果:
对于较大:click this link
疑问,你是寻找?
- 为什么Form1上(在图像中左侧)能破线槽锁,一个时间它等待锁被再次打开,但大多不会,即使它被调用的另一种方法,它应该使用相同的锁。我对结果感到满意,首先它不会使我的应用程序崩溃,其次它不会同时保存,但只能从相同的表单中保存,然后我猜。
- 我已经尝试使用Monitor.TryEnter与间隔,这导致有时离开锁锁我做了,就像我在那里调用方法:
LoadCertainData()
在数据库类... 现在修复此问题,正确的方法显示在代码 - 我认为这台显示器能够获取锁,以便他们进来,在我已经编号了什么顺序点击(datagridview)和它执行的顺序的图像。它没有按照正确的顺序进行,看起来最后一个出现在第一位。我不知道如何this queuing works 事实证明它没有。
- 最后但并非最不重要的是,这被认为是ThreadSafe?我正朝着好的方向前进吗? 认为没有第一个,仍然必须修复正确的方法返回对象(数据集)我想这不是一个好主意,使用相同的类都为非多线程方法和multitheading功能?
对于1.
我想这是因为我们创建数据库对象的另一个实例,在我看来静止目标应该可以解决这个问题,因为它在窗口2显然不。 编辑:根据@Hatchet,我说得对,但这让我想:我创建了6个新的form2实例,因此又创建了6个更多的ThreadDatabase
实例,那么为什么那些6在同步中,这意味着他们不同时获得一个锁。而form1可以同时获得一个锁。我注意到:当另一个线程(在form2中)已经获得了锁的时候,form1会一直等到它被打开。但是,一旦该锁打开,form1和form2都可以获取该锁。
我知道,在图像中,我可以很容易地编辑ID的表的,它只是一个测试,我知道我的周围
你不应该使用多线程。教程是可怕的过时。使用单线程异步方法。 – Aron
我认为这个问题会更适合于http://codereview.stackexchange.com/ – hatchet
我会检查一下Aron,谢谢,但我认为这是围绕多线程的全部魔法,让GUI在运行时保持平稳运行在后台的东西。 – CularBytes