Собственно долго ломал голову и так и не нашел ответа на вопрос почему...
задача простая... лог со скроллом вниз, Если количество строчек больше заданного числа, последняя строчка удаляется.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace test
{
public partial class Form1 : Form
{
private List<Thread> _threads;
private bool _working = true;
public Form1()
{
InitializeComponent();
_threads = new List<Thread>();
}
private void button1_Click(object sender, EventArgs e)
{
_working = true;
Thread t;
for (int i = 0; i < 2; i++)
{
t = new Thread(new ThreadStart(ExecuteThreadProc));
_threads.Add(t);
t.Start();
label1.Text = _threads.Count.ToString();
Application.DoEvents();
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_working = false;
}
private void ExecuteThreadProc()
{
int count = 0;
int threadID = Thread.CurrentThread.ManagedThreadId;
string str;
StringBuilder sb = new StringBuilder();
while(_working)
{
sb.Length = 0;
str = string.Format("ThreadID = {0}, Count={1}", threadID, count++);
for (int i = 0; i < 100; i++)
sb.AppendFormat("{0} ", threadID);
_OnMessage(str, sb.ToString());
Thread.Sleep(200);
}
}
private delegate void d_OnMessage(string str, string s);
private void _OnMessage(string str, string s)
{
if (InvokeRequired)
{
try
{
Monitor.Enter(listBox1);
int id = Thread.CurrentThread.ManagedThreadId; // это просто для контроля, чтобы быть уверенным что потоки действительно разные...
BeginInvoke(new d_OnMessage(_OnMessage), new object[] { str, s });
}
finally
{
Monitor.Exit(listBox1);
}
}
else
{
listBox1.BeginUpdate();
listBox1.Items.Insert(0, listBox1.Items.Count.ToString("D3") + " " + str + " --> " + s);
label1.Text = listBox1.Items.Count.ToString();
while (listBox1.Items.Count >= 10)
listBox1.Items.Remove(listBox1.Items.Count - 1); //!!! вот тут приложение ложится насмерть...
listBox1.EndUpdate();
}
}
private void button2_Click(object sender, EventArgs e)
{
_working = false;
_threads.Clear();
label1.Text = _threads.Count.ToString();
}
}
}
если я правильно понял... что Monitor.Enter Acquires an exclusive lock on the specified object. из МСДН.
значит код не должен одновременно выполняться одновременно... если убрать удаление, то работает
как только выполняется listBox1.Items.Remove(listBox1.Items.Count — 1); приложение ложится....
так работает ли Monitor? и в каких случаях, потому что в случае написанного в МСДН он не работает... или я не так понял?
Здравствуйте, Rumata_V, Вы писали:
R_V>Собственно долго ломал голову и так и не нашел ответа на вопрос почему...
R_V>задача простая... лог со скроллом вниз, Если количество строчек больше заданного числа, последняя строчка удаляется.
R_V>
R_V> publicpartialclass Form1 : Form
R_V>
во втором фреймворке есть семафоры, см. Semaphore, ипользуй их...
А какую из двух строчек кода защищает монитор и зачем?
try
{
Monitor.Enter(listBox1);
int id = Thread.CurrentThread.ManagedThreadId; // это просто для контроля, чтобы быть уверенным что потоки действительно разные...
BeginInvoke(new d_OnMessage(_OnMessage), new object[] { str, s });
}
finally
{
Monitor.Exit(listBox1);
}
Здравствуйте, Rumata_V, Вы писали:
R_V>если я правильно понял... что Monitor.Enter Acquires an exclusive lock on the specified object. из МСДН. R_V>значит код не должен одновременно выполняться одновременно... если убрать удаление, то работает R_V>как только выполняется listBox1.Items.Remove(listBox1.Items.Count — 1); приложение ложится....
R_V>так работает ли Monitor? и в каких случаях, потому что в случае написанного в МСДН он не работает... или я не так понял?
Используй не BeginInvoke, а просто Invoke. BeginInvoke работает асинхронно.
Здравствуйте, Rumata_V, Вы писали:
R_V>Собственно долго ломал голову и так и не нашел ответа на вопрос почему...
R_V>задача простая... лог со скроллом вниз, Если количество строчек больше заданного числа, последняя строчка удаляется.
public partial class Form1 : Form
{
private List<Thread> _threads = new List<Thread>();
private volatile bool _working = true;//К этой переменной идет обращение из нескольких поток,
//она должна быть volatilepublic Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
_working = true;
Thread t;
for (int i = 0; i < 2; i++)
{
t = new Thread(new ThreadStart(ExecuteThreadProc));
_threads.Add(t);
t.Start();
label1.Text = _threads.Count.ToString();
Application.DoEvents();
}
}
private void ExecuteThreadProc()
{
int count = 0;
int threadID = Thread.CurrentThread.ManagedThreadId;
string str;
StringBuilder sb = new StringBuilder();
while (_working)
{
sb.Length = 0;
str = string.Format("ThreadID = {0}, Count={1}", threadID, count++);
for (int i = 0; i < 100; i++)
sb.AppendFormat("{0} ", threadID);
_OnMessage(str, sb.ToString());
Thread.Sleep(200);
}
}
private delegate void d_OnMessage(string str, string s);
private void _OnMessage(string str, string s)
{
if (InvokeRequired)
{
//Здесь убран монитор. Т.к. нафиг не нужен - в монитор входит поток, а BeginInvoke всего лишь говорит главному потоку: исполни делегат
//и не дожидаясь выполнения метода выходит.
BeginInvoke(new d_OnMessage(_OnMessage), new object[] { str, s });
}
else
{
//Тут синхронизация тоже нафиг не нужна - все и так исполняется в одном, главном потоке формы.
listBox1.BeginUpdate();
listBox1.SuspendLayout();//Чтобы не мигало
listBox1.Items.Insert(0, listBox1.Items.Count.ToString("D3") + " " + str + " --> " + s);
label1.Text = listBox1.Items.Count.ToString();
while (listBox1.Items.Count >= 10)
listBox1.Items.RemoveAt(listBox1.Items.Count - 1);//Здесь был .Remove. А удалить надо не строку с числом,
//а последнюю строку. Т.е. это RemoveAt
listBox1.ResumeLayout();
listBox1.EndUpdate();
}
}
private void button2_Click(object sender, EventArgs e)
{
_working = false;
_threads.Clear();
label1.Text = _threads.Count.ToString();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_working = false;
}
}
К комментариям в коде добавлю, что для того, чтобы войти в монитор в C# есть специальная конструкция lock (см msdn).
Здравствуйте, chabster, Вы писали:
C>А какую из двух строчек кода защищает монитор и зачем?
C> try C> { C> Monitor.Enter(listBox1); C> int id = Thread.CurrentThread.ManagedThreadId; // это просто для контроля, чтобы быть уверенным что потоки действительно разные... C> BeginInvoke(new d_OnMessage(_OnMessage), new object[] { str, s }); C> } C> finally C> { C> Monitor.Exit(listBox1); C> }
да собственно эту
BeginInvoke(new d_OnMessage(_OnMessage), new object[] { str, s });
потому что при бесконтрольном вызове тут будет переполнение стека... хотя согласен глупый код.
Да и решение было не в этом а...
listBox1.Items.Remove(listBox1.Items.Count — 1);
надо заменить на
listBox1.Items.RemoveAt(listBox1.Items.Count — 1);
или
listBox1.Items.Remove(listBox1.Items[listBox1.Items.Count — 1]);
собственно я удивлен, что не выскакивала ошибка, а приложение валилось, потому грешил на синхронизацию...
Невнимательность ...
Здравствуйте, Rumata_V, Вы писали:
R_V>Да и решение было не в этом а...
R_V>listBox1.Items.Remove(listBox1.Items.Count — 1); R_V>надо заменить на R_V>listBox1.Items.RemoveAt(listBox1.Items.Count — 1); R_V>или R_V>listBox1.Items.Remove(listBox1.Items[listBox1.Items.Count — 1]);
Второй способ не подойдет в случае, если в ListBox есть еще одна строка равная последней.
Здравствуйте, Красин, Вы писали:
К>Здравствуйте, Rumata_V, Вы писали:
R_V>>Собственно долго ломал голову и так и не нашел ответа на вопрос почему...
R_V>>задача простая... лог со скроллом вниз, Если количество строчек больше заданного числа, последняя строчка удаляется. К>
К>public partial class Form1 : Form
К>{
К> private List<Thread> _threads = new List<Thread>();
К>private volatile bool _working = true;//К этой переменной идет обращение из нескольких поток,
К>//она должна быть volatile
К> public Form1()
К> {
К> InitializeComponent();
К> }
К> private void button1_Click(object sender, EventArgs e)
К> {
К> _working = true;
К> Thread t;
К> for (int i = 0; i < 2; i++)
К> {
К> t = new Thread(new ThreadStart(ExecuteThreadProc));
К> _threads.Add(t);
К> t.Start();
К> label1.Text = _threads.Count.ToString();
К> Application.DoEvents();
К> }
К> }
К> private void ExecuteThreadProc()
К> {
К> int count = 0;
К> int threadID = Thread.CurrentThread.ManagedThreadId;
К> string str;
К> StringBuilder sb = new StringBuilder();
К> while (_working)
К> {
К> sb.Length = 0;
К> str = string.Format("ThreadID = {0}, Count={1}", threadID, count++);
К> for (int i = 0; i < 100; i++)
К> sb.AppendFormat("{0} ", threadID);
К> _OnMessage(str, sb.ToString());
К> Thread.Sleep(200);
К> }
К> }
К> private delegate void d_OnMessage(string str, string s);
К> private void _OnMessage(string str, string s)
К> {
К> if (InvokeRequired)
К> {
К>//Здесь убран монитор. Т.к. нафиг не нужен - в монитор входит поток, а BeginInvoke всего лишь говорит главному потоку: исполни делегат
К>//и не дожидаясь выполнения метода выходит.
К> BeginInvoke(new d_OnMessage(_OnMessage), new object[] { str, s });
К> }
К> else
К> {
К>//Тут синхронизация тоже нафиг не нужна - все и так исполняется в одном, главном потоке формы.
К> listBox1.BeginUpdate();
К> listBox1.SuspendLayout();//Чтобы не мигало
К> listBox1.Items.Insert(0, listBox1.Items.Count.ToString("D3") + " " + str + " --> " + s);
К> label1.Text = listBox1.Items.Count.ToString();
К> while (listBox1.Items.Count >= 10)
К> listBox1.Items.RemoveAt(listBox1.Items.Count - 1);//Здесь был .Remove. А удалить надо не строку с числом,
К>//а последнюю строку. Т.е. это RemoveAt
К> listBox1.ResumeLayout();
К> listBox1.EndUpdate();
К> }
К> }
К> private void button2_Click(object sender, EventArgs e)
К> {
К> _working = false;
К> _threads.Clear();
К> label1.Text = _threads.Count.ToString();
К> }
К> private void Form1_FormClosing(object sender, FormClosingEventArgs e)
К> {
К> _working = false;
К> }
К>}
К>
К>К комментариям в коде добавлю, что для того, чтобы войти в монитор в C# есть специальная конструкция lock (см msdn).
спасибо, очень дельные советы... Избыточность кода... потому что не знал что делать, почему? Грешил на синхронизацию, потому писал локи и мониторы где попало...
ошибка была именно в listBox1.Items.RemoveAt(listBox1.Items.Count — 1);
ну можно было бы ещё listBox1.Items.RemoveAt(listBox1.Items[listBox1.Items.Count — 1]);
бес попутал... стер все, переписал код заново, заработало, потом проанализировал отличия... и вот...
Здравствуйте, Красин, Вы писали:
К>Здравствуйте, Rumata_V, Вы писали:
R_V>>Да и решение было не в этом а...
R_V>>listBox1.Items.Remove(listBox1.Items.Count — 1); R_V>>надо заменить на R_V>>listBox1.Items.RemoveAt(listBox1.Items.Count — 1); R_V>>или R_V>>listBox1.Items.Remove(listBox1.Items[listBox1.Items.Count — 1]);
К>Второй способ не подойдет в случае, если в ListBox есть еще одна строка равная последней.
ну это уже... лол...
хотя согласен, такое возможно, при этом удалится N строчек... с указателем на оригинал.
В данном случае я уверен, что указатели разные. они же генерятся потоками через стригбилдер.
Здравствуйте, Rumata_V, Вы писали:
К>>Второй способ не подойдет в случае, если в ListBox есть еще одна строка равная последней.
R_V>ну это уже... лол... R_V>хотя согласен, такое возможно, при этом удалится N строчек... с указателем на оригинал. R_V>В данном случае я уверен, что указатели разные. они же генерятся потоками через стригбилдер.
Нет. Т.к. ListBox проверяет не на то, что ссылка та же, а вызывает метод Equals, который сравнивает строки. В доказательство код: