WinForms - Как я могу получить доступ / вызвать методы в потоке пользовательского интерфейса из отдельного потока без передачи делегата? - PullRequest
1 голос
/ 27 марта 2010

ВОПРОС : Как получить доступ к / вызвать методы в потоке пользовательского интерфейса из отдельного потока в приложениях .NET 3.5 WinForms из отдельного потока без передачи делегата?

ПРИМЕР : скажем, у меня есть какой-то код, который я хочу запускать как (а) вручную, когда пользователь нажимает кнопку, так и (б) периодически вызываться процессом, который выполняется в отдельном не mainUI поток, но без передачи делегата. [Проще говоря, я думаю, что класс, имеющий этот метод, уже создан, и основной поток пользовательского интерфейса имеет дескриптор к нему, поэтому, если процесс, выполняющийся в отдельном потоке, может просто получить дескриптор от него поток главного пользовательского интерфейса это могло бы назвать это. Надеюсь, это не ошибочная концепция]

ОБЩАЯ ИНФОРМАЦИЯ : Я на самом деле ищу способ сделать это для случая, когда мой отдельный поток процесса на самом деле является работой, которую я планирую с помощью quartz.net. То, как работает планировщик, я не могу передать делегату. Есть способ передать JobDetails, однако кажется, что он обслуживает только такие вещи, как string, int и т. Д. Следовательно, мне нужен способ доступа к классу MainForm, например, для вызова метода из него изнутри Задание quartz.net, которое выполняется в отдельном потоке.

Спасибо

Ответы [ 4 ]

2 голосов
/ 27 марта 2010

Предполагая, что у вас есть доступ к MainForm, вы можете просто вызвать любой из его методов, но эти методы будут нести бремя проверки того, нужно ли их маршалировать в поток пользовательского интерфейса и обрабатывать делегат, проходящий там.

Итак, в вашей основной форме у вас может быть метод:

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

    // do actual update of progress here
}
1 голос
/ 27 марта 2010

Вы можете попробовать элемент управления BackgroundWorker на панели инструментов, это работает для простых вещей.

1 голос
/ 27 марта 2010

.Net не позволяет возиться с пользовательским интерфейсом из не-пользовательского потока; Есть способы обойти это, например Invoke, но это единственный способ (безопасно) сделать это, не прибегая к опросу общего объекта.

0 голосов
/ 16 мая 2016

Это один из подходов к приложению WinForms, если оно имеет форму «MainForm», которая всегда существует после инициализации приложения. Я кеширую ссылку на это в статической переменной, а затем у меня есть статический вспомогательный метод, который я использую во всех методах, которым необходим доступ к пользовательскому интерфейсу, и которые могут вызываться из потоков, не связанных с пользовательским интерфейсом.

Что мне нравится в этом подходе, так это то, что после начальной настройки вы можете написать код, касающийся интерфейса пользователя, в ЛЮБОМ классе, а не только в классах, которые являются элементами управления. А кодирование - это просто вопрос оборачивания действия в вызове MyApp.RunOnUIThread. См. Определения для SomeUIWork1, SomeUIWork2 и SomeUIWork3 для вариантов этого.


Ограничения и предостережения:

  • Если ваше приложение не имеет формы, которая всегда существует, или по какой-то причине у вас есть несколько потоков пользовательского интерфейса, то это решение нужно будет адаптировать, или оно может быть бесполезным для вас.

  • Этот подход, как и все подходы, включающие RequireInvoke или аналогичные тесты, может быть ПЕРЕОБРАЗОВАН, что приводит к сложной для обслуживания / понимания системе. Рекомендуется только в крайнем случае. (Я использовал его при расширении устаревшего кода, когда было слишком много времени на разработку, чтобы безопасно реорганизовать существующий код.)

Если это целесообразно, вместо того, чтобы делать то, что я делаю здесь, отделите ваш код пользовательского интерфейса от кода не-пользовательского интерфейса Например, используйте BackgroundWorker с progressChanged https://stackoverflow.com/a/10646636/199364.


В 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
}

В 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
...