Как выполнить вызовы метода пользовательского класса в отдельном потоке? - PullRequest
2 голосов
/ 09 августа 2011

У меня есть пользовательский интерфейс, пользовательский класс и поток.Я хочу запустить пользовательский класс полностью в отдельном потоке.Есть ли чистый способ сделать это?

Например.В MainForm ниже, когда пользовательский интерфейс вызывает _threadOneClass.Sleep, мне нужно, чтобы пользовательский интерфейс перешел в порожденный ThreadOne и вызвал метод Sleep в ThreadOne, а не в основном потоке.

По сути, все вызовы методов в MyClass требуютвыполняется в ThreadOne, а не в основном потоке.Это похоже на то, что MyClass работает по своему собственному «процессу», хотя все еще видимым для вызова из MainForm.

MainForm имеет 3 кнопки и 1 текстовое поле для ведения журнала.

Я думалполучения класса Thread, но он запечатан.Таким образом, вывод определенно неправильный способ для Microsoft.

Помогите уважаемые эксперты?

Вот вывод (MainThread ID = 10, ThreadOne ID = 11)

MyClass instantiated
Starting ThreadOne
11-Run.start
Sleeping ThreadOne
10-Run.sleep for 3000    'Need this to run on ThreadID 11
10-Run.woke up           'Need this to run on ThreadID 11
Stopping ThreadOne
11-Run.done

Вот как выглядит код.

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    private Thread _threadOneThread;
    private MyClass _threadOneClass;

    private void btnThreadOneCreate_Click(object sender, EventArgs e)
    {
        _threadOneClass = new MyClass(this);
        _threadOneThread = new Thread(new ThreadStart(_threadOneClass.Run));
        _threadOneThread.Start();
    }

    private void btnThreadOneStop_Click(object sender, EventArgs e)
    {
        _threadOneClass.IsRunning = false;
    }

    private void btnThreadOneSleep_Click(object sender, EventArgs e)
    {
        _threadOneClass.Sleep(3000);
    }

    public void Log(string txt)
    {
        MainForm.SetText(txtLog, txt);
    }

    internal static void SetText(Control ctl, string val)
    {
        if (ctl.InvokeRequired)
            ctl.Invoke((MethodInvoker)delegate() { ctl.Text += Environment.NewLine + val; });
        else
            ctl.Text += Environment.NewLine + val;
    }
}

class MyClass
{
    public MyClass(MainForm frm)
    {
        _mainForm = frm;
    }
    private MainForm _mainForm;
    public bool IsRunning = true;
    public void Run()
    {
        _mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.start");
        while (IsRunning) { }
        _mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.done");
    }

    public void Sleep(int milliseconds)
    {
        _mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.sleep for " + milliseconds.ToString());
        Thread.Sleep(milliseconds);
        _mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.woke up");
    }
}

Ответы [ 4 ]

2 голосов
/ 15 мая 2012

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

К счастью, ребята из Microsoftсделал чрезвычайно простым для написания того же кода, но в асинхронном режиме.Я обычно использую Задачи, потому что мне нравится элемент управления, который вы получаете над операцией, а также ContinueWith (), позволяющий вам контролировать то, что вы делаете с результатом, если вам нужно передать данные обратно в вызывающий поток.Если вы предпочитаете использовать потоки, ThreadPool.QueueUserWorkItem такой же простой.

Любая операция, которую вы не хотите блокировать потоком пользовательского интерфейса, оберните его следующим образом:

Task.Factory.StartNew(() => Object.PerformOperation());

или

ThreadPool.QueueUserWorkItem(new WaitCallback((x) => Object.PeroformOperation()));

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

Task.Factory.StartNew(() =>
{
    // do something
    // do more stuff
    // done
}).ContinueWith((completedTask) =>
{
    // if you were computing a value with the task
    // you can now do something with it
    // this is like a callback method, but defined inline

    // use ui's dispatcher if you need to interact with ui compontents
    UI.Label.Dispatcher.Invoke(new Action(() =>
         UI.Item.Label.Text = completedTask.Result;
}

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

// this will begin the operation, then return control back to the ui so it does not hang. 
var result = await Object.PerformLongTask(); 

// once the long task is completed then it continues and you can use the result
UI.Item.Label = result;

Чтобы привести реальный пример, вот некоторый код написанного мной FTP-клиента, который имеет фронт WPFконец.Когда нажата кнопка запуска, передача ftp запускается в ее собственной задаче, затем в задаче запускается цикл while, который обновляет интерфейс каждые полсекунды, так что ни один из них не мешает потоку интерфейса.Опять тот же код, только что завернутый в код лямбады.

    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
        Task.Factory.StartNew(() =>
            ftp.Mirror(@"C:\LocalFolder", "/RemoteFolder", 10));

        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                lbPercentSuccess.Dispatcher.Invoke(new Action(() =>
                {
                    lbPercentSuccess.Content = ftp.FtpProgress.SuccessPercentage;
                    lbPercentError.Content = ftp.FtpProgress.ErrorPercentage;
                    lbPercentTotal.Content = ftp.FtpProgress.TotalPercentage;
                    lbDuration.Content = ftp.FtpProgress.Duration;
                }));

                Thread.Sleep(500);
            }
        });
    } 
1 голос
/ 11 августа 2011

Это не просто, но взгляните на это. Это хорошее объяснение того, как использовать межпотоковую связь.

http://www.codeproject.com/KB/cs/delegatequeue.aspx

1 голос
/ 10 августа 2011

Насколько я знаю, это невозможно. Вы можете запускать и вызывать отдельные методы или ставить их в очередь только в отдельных потоках, когда это необходимо. Установка реального объекта в отдельном потоке наносит ущерб вашей цели. Это потому, что вы будете использовать преимущества многопоточности только при вызове метода в отдельном потоке, а не в объекте.

затем переназначить del для MethodTwo ... и так далее. Это становится проще, если вы соответствуете сигнатуре метода.

Возможное решение:

Thread threadTest = new Thread(new ThreadStart(MethodOne));
   threadTest = new Thread(new ThreadStart(MethodTwo));
   threadTest.Start();

Или

    Action del = TestClass.MethodOne;
   IAsyncResult result = del.BeginInvoke(null, null);
   Func<int,int> del = TestClass.MethodOne;
   IAsyncResult result = del.BeginInvoke(11,null, null);
   int value = del.EndInvoke(result);
0 голосов
/ 16 августа 2011

Пока что это то, что я нашел (из разработки для iPhone).Цикл Run действует как позвоночник, который вызывает различные методы.Это реализовано следующим образом:Приветствуется более элегантное решение.

class MyClass
{
    public MyClass(MainForm frm)
    {
        _mainForm = frm;
    }
    private MainForm _mainForm;
    public bool IsRunning = true;
    public void Run()
    {
        _mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.start");
        while (IsRunning)
        {
            if (_runSleepMilliSeconds != null)
            {
                _Sleep(_runSleepMilliSeconds ?? 3000);
                _runSleepMilliSeconds = null;
            }   
        }
        _mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.done");
    }

    private int? _runSleepMilliSeconds = null;
    public void Sleep(int milliseconds)
    {
        _runSleepMilliSeconds = milliseconds;
    }
    private void _Sleep(int milliseconds)
    {
        _mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.sleep for " + milliseconds.ToString());
        Thread.Sleep(milliseconds);
        _mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.woke up");
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...