8

I have a winforms app with multiple GUI threads. I want them to be able to access each other's thread objects without having to keep track of that information separately.

Is there a function in .NET that I can feed a winforms control or window object, and get back the thread? Or a function in the API I can pinvoke for the threadID?

(please no comments saying I should do it another way... also this is not about cross-thread window operations.)

Thanks!

Edit

For those of you who for some reason believed my italicized text, congratualations, you're hired!! Here is the problem:

"App is crashing in the wild by locking up totally, that is, it stop responding. Very intermittent, and trying to debug it, it seems to never happen."

So what do do? Install an option in the program that the user can activate under our direction, whereby from another GUI thread in the same app, do a thread.abort on the main GUI thread, then we can look at the call stack in the error log. Viola, found an impossible to debug error in less than a day. (Stop now, it had nothing to do with abusing multithreading:-)

I'll admit I almost didn't ask this, the reason I did was I could see an object reference to the main form, but there wasn't any for its thread. I'm giving Chris Shain the answer a/c it is a quick way, unfortunately when the thread is hanging, I wouldn't be able to do an invoke (it would hang too). A little more digging revealed the GetWindowThreadProcessId API call. But it's an unmanaged thread ID, apparently there are complications turning that into a managed thread ID.

So I bit the bullet and put in a global reference to the main UI thread. Would have posted it to begin with, but hadn't written it yet.

Now if you'll pardon the VB...

In main public module/static class:

Public GUIThread As Threading.Thread
Sub Main()
    '' //  Create app main window
    ShellForm = New frmShell
    '' // Save main GUI Thread for abort routine
    GUIThread = Threading.Thread.CurrentThread  
    If GetSetting("MyApp", "Testing", "CrashDebug", "False") = "True" Then
            '' //  DO NOT run the pgm. like this normally - with try/catch around
            '' //  Application.Run - or uncaught errors will kill the whole app!!!
        Try

            '' // This is the other of the ‘Multiple GUI threads’ I talked
            '' // about in the Orig Post.
            Dim t As New Threading.Thread(AddressOf StartCrashDebug)
            t.Start()

            Application.Run(ShellForm)
        Catch ex As Exception
            '' // This error routine passes errors off to another thread which 
            '' // logs them (and also shows messages)
            MyLogError(ex, "CrashDebug - Main Window blew up")
        End Try
    Else
        '' // Normal mode - uncaught errors will get caught by UnhandledException, 
        '' // logged, and Winforms will keep the GUI alive (since we _do_ care 
        '' // more about users than computers right ;-)
        Application.Run(ShellForm)
    End If
End Sub
Sub StartCrashDebug()
    Dim f As New frmCrashFinder
    '' // Starting a window like this on a separate thread makes it ‘Another 
    '' // GUI thread’ for winforms, by design
    Application.Run(f)
 End Sub

In ‘aborter’ WinForm:

Public Class frmCrashFinder 
    Inherits Windows.Form

    Private Sub Abort_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Abort.Click
        GUIThread.Abort()
    End Sub
End Class
FastAl
  • 6,194
  • 2
  • 36
  • 60
  • 7
    *please no comments saying I should do it another way* Right, because yours is the only possible solution to the problem. Don't assume that no one could possibly come up with a better idea than you already have. – Ed S. Feb 01 '11 at 19:13
  • 6
    This makes no sense, a thread doesn't "own" an object. – Hans Passant Feb 01 '11 at 19:16
  • 2
    Note that your question's title is not consistent with what you're actually asking in the question body. – stakx - no longer contributing Feb 01 '11 at 19:41
  • 1
    @Ed S. (For the record he said this before I added the edit)...I would be happy to use a better solution to this particular problem if I could get one! Of course I didn't think this would actually work, either! – FastAl Feb 03 '11 at 21:21
  • @stakx, @Hans Passant, apologies for the poor description; I got lousy grades in English. Do an application.run(winform1) from Main() thread; start thread2 that does an application.run(winform2). mainthread is first GUI thread, 'belonging' (or vise-versa) to winform1 and any other windows that that thread makes; likewise with thread2 and winform2. – FastAl Feb 03 '11 at 21:39
  • +1 because I also had a debugging situation where I needed to do something like this. But also +1 to the first three comments, because before the edit addition, this was a poor question, that warranted responses like those. An example of why one should always EXPLAIN the situation that one is trying to solve. – ToolmakerSteve Jun 08 '15 at 17:38

6 Answers6

13

All GUI elements in Windows Forms are typically done on a single thread. I strongly recommend avoiding trying to do this any other way.

You can always marshal code to that thread by using Control.Invoke or Control.BeginInvoke with any Control.

If you really want to get the thread's ID (not sure what use this will be..?), you could use:

int GetControlThreadId(Control control)
{
    int threadId;
    control.Invoke( new Action( () => 
       {
           threadId = Thread.CurrentThread.ManagedThreadId;
       }));
    return threadId;
}
Mike de Klerk
  • 11,906
  • 8
  • 54
  • 76
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Once you have a `Thread` object or `ThreadId`, there is no rational way to retrieve "data" from the thread. Retrieving data from a separate thread is probably symptomatic of a need for a data object given to each thread that feeds the GUI. – user7116 Feb 01 '11 at 19:40
  • @sixlettervariables: Yes - this is absolutely useless code, in my opinion... Just showing *how* it would be done, but still see no reason for it... – Reed Copsey Feb 01 '11 at 19:45
  • 1
    @FastAI: The edit certainly explains why you wanted the thread ID. However, your question before the edit included a bit about, "[accessing] each other's thread objects," which is what kept us from determining the "correct answer". – user7116 Feb 03 '11 at 22:54
10

If your code is not in a form or control, you can use

if (System.Windows.Forms.Form.ActiveForm.InvokeRequired)
{
    System.Windows.Forms.Form.ActiveForm.Invoke(...);
}
Metro
  • 1,464
  • 14
  • 24
  • When my window minimizes, it gets a `NullReferenceException` (as expected, because its not the active window anymore). How do I get the GUIThread and invoke things on it on my will? – Vandrey Apr 21 '20 at 16:23
  • 1
    If it is because you want to update something on the window, you must have a reference to the window. Then you could just use the window reference rather than "System.Windows.Forms.Form.ActiveForm". However, if that is the case, I would create a Method on the form to perform the desired action and then handle the threading issue in the Method. See https://stackoverflow.com/a/25903258/18978 – Metro Apr 22 '20 at 16:06
  • Thank you so much! I'll try it that way. – Vandrey Apr 22 '20 at 17:48
9

This should do it, however I agree with other posters that this is probably the wrong thing to do for other reasons...

var thatWindowsThread = (Thread)(WhateverWindow.Invoke(()=>Thread.CurrentThread);
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
3

WindowsFormsSynchronizationContext.Current has Post and Send methods from which you can delegate command to UI thread

Chingiz Musayev
  • 2,832
  • 1
  • 14
  • 6
1

If you don't have access to any forms or windows or controls you can pull the thread or SynchronizationContext from, you can use

        System.Windows.Forms.Application.OpenForms    

This worked for me. System.Windows.Forms.Form.ActiveForm was null in my case, but Metro's answer made me look closer at static classes in System.Windows.Forms. Use

System.Windows.Forms.Application.OpenForms.Invoke(...) or BeginInvoke. 

You can get the rest from other answers.

ILIA BROUDNO
  • 1,539
  • 17
  • 24
0

Better answer:

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsGUIThread([MarshalAs(UnmanagedType.Bool)] bool bConvert);

http://pinvoke.net/default.aspx/Constants.IsGUIThread