WinForms - How do I access/call methods in UI thread from a separate thread without passing a delegate?

别来无恙 提交于 2019-11-28 10:52:12

问题


QUESTION: In .NET 3.5 WinForms apps, how do I access/call methods in UI thread from a separate thread, without passing a delegate?

EXAMPLE: Say I have some code I want to run both (a) manually when the user clicks a button, and (b) periodically called by a process which is running in a separate non-mainUI thread but without passing a delegate. [Simplistically I'm thinking that the class that has this method is already been constructed, and the main UI thread has a handle to it, therefore if the process running in the separate thread could just get a handle to it from the main-UI thread it could call it. Hopefully this is not a flawed concept]

BACKGROUND: I'm actually after a way to do the above for the case where my separate process thread is actually a job I schedule using quartz.net. The way the scheduler works I can't seem to actually pass in a delegate. There is a way to pass JobDetails, however it only seems to caters for things like string, int, etc. Hence what I'm after is a way to access the MainForm class for example, to call a method on it, from within the quartz.net job which runs in a separate thread.

Thanks


回答1:


Assuming you have access to the MainForm, you can simply call any of it's methods, but those methods will then bear the burden of checking if they need to be marshalled to the UI thread and handle the delegate passing there.

So on your main form you could have a method:

public void UpdateProgress()
{
    if( this.InvokeRequired )
    {
        this.Invoke(UpdateProgress);
        return;
    }

    // do actual update of progress here
}



回答2:


.Net doesn't allow fiddling with the UI from a non-UI thread; There are ways around it such as Invoke, but this is the only way to (safely) do it without resorting to polling a shared object.




回答3:


You could try the BackgroundWorker control in the toolbox, this works for simple things.




回答4:


This is one approach for a WinForms app, if it has a form "MainForm" that always exists once the app is initialized. I cache a reference to that in a static variable, and then have a static helper method that I use in all methods that need access to UI, and might be called from non-UI threads.

What I like about this approach, is that after the initial setup, you can write UI-touching code in ANY class, not just classes that are controls. And the coding is a simple matter of wrapping an action inside a call to MyApp.RunOnUIThread. See the definitions for SomeUIWork1, SomeUIWork2, and SomeUIWork3 for variations on this.


Limitations and Caveats:

  • If your app does not have a form that always exists, or for some reason you have MULTIPLE UI threads, then this solution will need to be adapted, or may not be useful to you.

  • This approach, like all approaches involving RequireInvoke or similar tests, can be OVERUSED, resulting in a hard-to-maintain/understand system. It is recommended only as a last resort. (I used it when enhancing legacy code, where it was too much development time to safely refactor the existing code.)

If practical, instead of doing what I do here, separate your UI code from non-UI code. For example, use BackgroundWorker with progressChanged https://stackoverflow.com/a/10646636/199364.


In C#:

public static class MyApp
{
    public static MainForm mainForm;

    public static void RunOnUIThread(Action action)
    {
        if (mainForm.InvokeRequired)
            mainForm.Invoke(action);
        else
            action();
    }
}


// In the actual project, the Form inheritance is in the Visual Designer file for this form.
public class MainForm : System.Windows.Forms.Form
{

    public MainForm()
    {
        // Defined in the Visual Designer for this form.
        InitializeComponent();

        MyApp.mainForm = this;
    }
}


public class SomeClass
{
    public void SomeMethod()
    {
        // ... do some work ...

        SomeUIWork1(true);

        // ... do some work ...

        SomeUIWork2();

        // ... do some work ...

        SomeUIWork3(true);
    }

    // This accesses UI elements, yet is safe to call from non-UI thread.
    // Shows inline code.
    public void SomeUIWork1(bool param1)
    {
        MyApp.RunOnUIThread(() =>
        {
            // ... do the UI work ...
        });
    }

    // This accesses UI elements, yet is safe to call from non-UI thread.
    // Shows calling a separate method, when no parameters.
    public void SomeUIWork2()
    {
        MyApp.RunOnUIThread(SomeUIWork2_AlreadyOnUIThread);
    }

    // This accesses UI elements, yet is safe to call from non-UI thread.
    // Shows calling a separate method, when there are parameters.
    public void SomeUIWork3(bool param1)
    {
        MyApp.RunOnUIThread(() => SomeUIWork3_AlreadyOnUIThread(param1));
    }


#region "=== Only call from UI thread ==="
    // Only call if you are certain that you are running on UI thread.
    private void SomeUIWork2_AlreadyOnUIThread()
    {
        // ... do the UI work ...
    }

    // Only call if you are certain that you are running on UI thread.
    private void SomeUIWork3_AlreadyOnUIThread(bool param1)
    {
        // ... do the UI work ...
    }
#endregion
}

In VB:

Imports Microsoft.VisualBasic

Public Shared Class MyApp
    Public MainForm As MainForm

    Public Sub RunOnUIThread(action As Action)
        If MainForm.InvokeRequired Then
            MainForm.Invoke(action)
        Else
            action()
        End If
    End Sub
End Class

' In the actual project, the "Inherits" line is in the Visual Designer file for this form.
Public Class MainForm
    Inherits System.Windows.Forms.Form   ' Or whatever type you are customizing

    Sub New()
        ' This call is required by the designer.
        InitializeComponent()

        MyApp.MainForm = Me
    End Sub
End Class

Public Class SomeClass
    Public Sub SomeSub()
        ' ... do some work ...

        SomeUIWork1(True)

        ' ... do some work ...

        SomeUIWork2()

        ' ... do some work ...

        SomeUIWork3(True)
    End Sub

    ' This accesses UI elements.
    ' Shows inline code.
    Public Sub SomeUIWork1(param1 As Boolean)
        MyApp.RunOnUIThread(
            Sub()
                ' ... do the UI work ...
            End Sub)
    End Sub

    ' This accesses UI elements.
    ' Shows calling a separate method, when no parameters.
    Public Sub SomeUIWork2()
        MyApp.RunOnUIThread(SomeUIWork_AlreadyOnUIThread)
    End Sub

    ' This accesses UI elements.
    ' Shows calling a separate method, when there are parameters..
    Public Sub SomeUIWork3(param1 As Boolean)
        MyApp.RunOnUIThread(Sub() SomeUIWork_AlreadyOnUIThread(param1))
    End Sub


#Region "=== Only call from UI thread ==="
    ' Only call if you are certain that you are running on UI thread.
    Private Sub SomeUIWork2_AlreadyOnUIThread()
        ' ... do the UI work ...
    End Sub

    ' Only call if you are certain that you are running on UI thread.
    Private Sub SomeUIWork3_AlreadyOnUIThread(param1 As Boolean)
        ' ... do the UI work ...
    End Sub
#End Region
End Class


来源:https://stackoverflow.com/questions/2526725/winforms-how-do-i-access-call-methods-in-ui-thread-from-a-separate-thread-with

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!