![]() |
|
![]() |
|
|
Thread Tools | Display Modes |
|
|
#1 |
|
Programmer
|
Hi. I have a problem, and don't have a clue how to solve it.
Application contains 1 form, with 3 ListView controls in it. First of them lists known file types with their current icons, the second one lists programs that are recommended for opening or editing files with extension you choose from the first ListView (displays icons too), while the third one displays all icons from the default program that opens files with the selected extension. When I use the application by mouse clicking from time to time it works fine. But if I start clicking more often, or start to use keyboard, for example when I just hold the down button to go through first ListView, the application goes crazy. It starts drawing what it shouldn't draw, and doesn't draw what it should. Eventually it crashes, reporting error, allways in different place. Offcourse, I fill second and third ListView in another thread. In the method listView1_SelectedIndexChanged i've tried to wait for the thread to finish, i've tried mutex synchronization... Nothing helps. I think that simple synchronization doesn't help when you work with forms. Can someone help me? Does anyone know what is, and how to use Control.Invoke(delegate) method, and does it have anything to do with this? |
|
|
|
|
|
#2 |
|
Expert Programmer
Join Date: Jun 2005
Posts: 850
Rep Power: 4
![]() |
Do you have only one other thread, or do you start a new thread for each SelectIndexChanged event?
One option would be to have only one second thread and have it only update on the last change, rather than all the ones in between. Have a variable that lists the currently selected list item in the first list. When you get the listView1_SelectedIndexChanged message, just update the variable and tell the second thread that the variable has changed (use a semaphor, or something similar). The second thread only looks at this variable after it has finished processing the last change, so that it completes one update before starting the next. If this is not very understandable, post some code so I can look at it. |
|
|
|
|
|
#3 |
|
Programmer
|
Thanks for help. But i'm not sure that would help. I've tried a lot of stuff.
Let's say that you have a ListView, and every SelectedIndexChanged event is causing a slow operation to be executed. For example some hard drive operation like extracting icons from some exe or dll file, and drawing those icons somewhere else. You want to move fast through ListView, and if previous operation has not finished, you want it to be terminated immediately, and the new one to start. How would you do this? |
|
|
|
|
|
#4 |
|
Expert Programmer
Join Date: Jun 2005
Posts: 850
Rep Power: 4
![]() |
In form1.txt below is an example program (just save it as form1.cs) which shows what I mean.
It is just a simple app with 2 listboxes. On startup it load all the directories in your C:\ into listBox1. When listbox1 selection changes it calls a worker thread to load the files in the selected directory into listBox2. Here is the main part of it (the the selection change event handler just calls setDirectoryToLoad void setDirectoryToLoad(string dirName)
{
m_workerMutex.WaitOne();
if (m_workerThread == null)
{
m_workerThread = new Thread( new ThreadStart(getFilesThread) );
m_workerThread.Start();
}
m_directoryToLoad = dirName;
m_workerMutex.ReleaseMutex();
m_workerStarter.Set();
}
void getFilesThread()
{
string lastDirectoryDone = "";
while (true)
{
// Wait for a start command
m_workerStarter.WaitOne();
// Lock the mutex for the shared string
m_workerMutex.WaitOne();
string dirName = m_directoryToLoad;
m_workerMutex.ReleaseMutex();
if (dirName != lastDirectoryDone)
{
lastDirectoryDone = dirName;
listBox2.Items.Clear();
try
{
string[] dirs = Directory.GetFiles(listBox1.GetItemText(listBox1.SelectedItem));
foreach (string dir in dirs)
{
Thread.Sleep(100);
listBox2.Items.Add(dir);
// Check to see if the dirname has been changed
m_workerMutex.WaitOne();
dirName = m_directoryToLoad;
m_workerMutex.ReleaseMutex();
if (dirName != lastDirectoryDone)
{
// Ensure that the next directory is done (in case it gets changed back before we loop around)
lastDirectoryDone = "";
break;
}
}
}
catch (UnauthorizedAccessException)
{
}
}
}
}setDirectoryToLoad starts up the worker thread if it hasn't already been started. Then it sets up the desired directory into a member variable and uses an event to tell the worker thread to go. A mutex is used to protect access to the directory name member variable. The getFilesThread function loops around waiting for the event to be triggered. When it gets the event, it grabs the directory name from the member variable (again, using the mutex to prevent concurrent access). If the directory name is different than last time, it loads it into Listbox2 with a delay between each load to simulate doing lots of work. It also does a check after each entry is loaded into listbox2 to see if there is a different directory requested. |
|
|
|
|
|
#5 |
|
Hobbyist Programmer
Join Date: Oct 2005
Posts: 211
Rep Power: 3
![]() |
I'd say you're pretty close dark, but you still have the chance of thread unsafety. The fact that the problem presents itself when the arrow keys are used instead of the mouse leads me to think the problem doesn't lie in a conflict between the OP's code with itself, but the OP's code and the control code.
.NET controls are not inheriently threadsafe, and to get around that you need to call .Invoke() on the control to ensure the specified control code gets executed in the proper thread. so anyplace inside the mutex that calls a function on listBox2 (or a similar control) should call the .Invoke method instead. Ex: listBox2.Items.Add(dir); should instead call listBox2.Invoke(myAdd, dir); which invokes the function myAdd in the proper thread and myAdd should then add dir to the listBox items accordingly. (If you need to do this in compact framework calling .Invoke with parameters is not supportedhttp://www.iter.dk/ has a download for PocketGPS which has a class called ControlInvoker which affectively gets around this restriction). -MBirchmeier |
|
|
|
|
|
#6 |
|
Expert Programmer
Join Date: Jun 2005
Posts: 850
Rep Power: 4
![]() |
Thanks MBirchmeier, I didn't know about the Invoke method (I assumed that they were thread-safe - bad assumption).
I assumed that the OPs code was actually starting a new thread for each message, and this was causing the problem. I asked, but he didn't answer and he didn't post any code. |
|
|
|
|
|
#7 |
|
Programmer
|
Thanks guys, I really appreciate your efforts to help. The Dark, that's very good idea, but application still isn't working properly. When I wait for both listViews to be populated it works fine, but if I don't the program goes crazy. Some icons, even in the first listView start to disappear, strange stuff start to go on, and application breaks. For example, when I go fast, app breaks on some extension, but when I run it again, and click on the same extension, it works ok.
I don't know what to do. This is the first time I have this kind of problem. Here is some code, maybe someone will figure out what might be the problem. private void listView1_SelectedIndexChanged(object sender, System.EventArgs e)
{
foreach(ListViewItem item in listView1.SelectedItems)
{
if (thrInfo == null)
{
thrInfo = new Thread( new ThreadStart(getInfo) );
thrInfo.Start();
}
mut.WaitOne();
ext="."+item.Text;
mut.ReleaseMutex();
areStarter.Set();
break;
}
}private void getInfo()
{
while(true)
{
areStarter.WaitOne();
mut.WaitOne();
string ext1=ext;
mut.ReleaseMutex();
listView2.Items.Clear();
imageList2.Images.Clear();
listView3.Items.Clear();
imageList3.Images.Clear();
appPath=RegEdit.GetAppPath(ext1);
pictureBox1.Image=null;
label2.Text=Shell.GetFileDesc(appPath);
writeOpenWithList(ext1);
if(appPath!="")
{
Icon ico=Shell.GetLargeIconFromFile(appPath);
if(ico!=null) pictureBox1.Image=ico.ToBitmap();
drawIcons();
}
}
}private void writeOpenWithList(string ext1)
{
ArrayList rez=null;
rez=RegEdit.GetOpenWithList(ext1);
if(rez.IndexOf(appPath)==-1) rez.Add(appPath);
foreach(object o in rez)
{
if(o.ToString().Trim()!="")
{
Icon ico=Shell.GetSmallIconFromFile(o.ToString());
if(ico!=null)
{
imageList2.Images.Add(ico);
listView2.Items.Add(Shell.GetFileDesc(o.ToString()),imageList2.Images.Count-1);
}
}
}
}private void drawIcons()
{
Icon[] iconList=Shell.IconsFromFile(appPath);
foreach(Icon icon in iconList)
if(icon!=null) imageList3.Images.Add(icon);
for(int i=0;i<imageList3.Images.Count;i++) listView3.ItemsAdd("",i);
}I've tried with Control.Invoke(delegate) method too, but it doesn't help. |
|
|
|
|
|
#8 |
|
Expert Programmer
Join Date: Jun 2005
Posts: 850
Rep Power: 4
![]() |
Whe you tried with the Control.Invoke method, did you change all of the calls to us Invoke? This means any call in the second thread to a method of listView2, listView3, pictureBox1, imageList2, imageList3 and label2.
Can you zip up your project and attach it, so I can see the behaviour? If you don't want to post it here, PM me and I'll send you my email address. |
|
|
|
![]() |
| Bookmarks |
| Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
| Thread Tools | |
| Display Modes | |
|
|