Common information
In most cases, SDK objects are created and managed within the main thread of a Windows Forms application. However, certain scenarios require invoking methods on WinForms UI controls from a thread different from the one in which the controls were created. Attempting this can lead to the “Cross-thread operation not valid” exception.
The result of the following example will be an exception:
MPreviewClass m_objPreview = new MPreviewClass();
System.Threading.Tasks.Task.Run(() =>
{
m_objPreview.PreviewEnable("", 1, 1);
m_objPreview.PreviewWindowSet("", PreviewPanel.Handle.ToInt32());
});
In this example, the MPreviewClass instance is created in the main UI thread. However, the .PreviewWindowSet() method is invoked from a different thread while attempting to access the PreviewPanel property, leading to an exception.
While multithreading can enhance application performance, Windows Forms controls are not inherently thread-safe. Accessing controls from multiple threads can introduce serious issues such as:
-
Race conditions
-
Deadlocks
-
Freezes or application hangs
To ensure thread safety, any cross-thread interaction with UI controls must be handled properly. There are two primary approaches to safely handle cross-thread operations:
Approach 1: Using Delegates (Recommended)
The preferred method involves invoking the delegate on the main UI thread using Invoke():
System.Threading.Tasks.Task.Run(() =>
{
m_objPreview.PreviewEnable("", 1, 1);
PreviewPanel.Invoke((MethodInvoker)delegate
{
m_objPreview.PreviewWindowSet("", PreviewPanel.Handle.ToInt32());
});
});
Approach 2: Disabling Cross-Thread Checks (Not Recommended)
Another approach involves disabling the enforcement of cross-thread access restrictions using:
Control.CheckForIllegalCrossThreadCalls = false;
While this method may seem like a quick fix, it is strongly discouraged as it can lead to unpredictable behavior and difficult-to-debug issues when accessing UI elements across threads.
Example Use Case: Adding Files to a Playlist
A common scenario where SDK objects interact with WinForms controls is adding files to a playlist. If done in the main UI thread, the interface may freeze during file addition. To prevent UI freezes, the file loading process should be performed in a separate thread:
if (openMediaFile.ShowDialog() == DialogResult.OK && openMediaFile.FileNames.Length != 0)
{
Task.Run(() =>
{
for (int i = 0; i < openMediaFile.FileNames.Length; i++)
{
MItem pFile = null;
int nIndex = -1;
m_objPlaylist.PlaylistAdd(null, openMediaFile.FileNames[i], "", ref nIndex, out pFile);
//update gui list
this.Invoke((MethodInvoker)delegate
{
updateList();
});
if (pFile != null)
Marshal.ReleaseComObject(pFile);
}
});
GC.Collect();
}
By using Invoke(), UI updates are handled on the main thread, ensuring thread safety and preventing UI-related crashes.