如何在单独的线程中执行自定义类的方法调用?

时间:2011-08-09 01:07:50

标签: c# .net winforms multithreading

我有一个UI,一个自定义类和一个线程。我想在一个单独的线程中完全运行自定义类。这样做有干净的方法吗?

例如。在下面的MainForm上,当UI调用_threadOneClass.Sleep时,我需要UI转到生成的ThreadOne并在ThreadOne中调用Sleep方法,而不是在主线程中。

基本上,MyClass中的所有方法调用都需要在ThreadOne中执行,而不是在主线程中执行。就像,MyClass运行在它自己的“进程”上,同时仍然可以从MainForm调用。

MainForm有3个按钮和1个用于记录的文本框。

我正在考虑派生Thread类,但它是密封的。因此,每个微软的推导绝对是一种错误的方式。

帮助亲爱的专家?

这是输出(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 个答案:

答案 0 :(得分:2)

线程允许您在继续执行其他操作时运行繁重的操作。对于用户界面(您的场景),异步行为几乎总是必要的,因为阻止UI线程将导致对用户没有响应,并且不是一个选项。

幸运的是,微软的人们以相同的方式编写相同的代码非常容易。我通常使用Tasks因为我喜欢你操作的控件以及ContinueWith()允许你控制你对结果做什么,如果你需要将数据传播回调用线程。如果您更喜欢使用线程,ThreadPool.QueueUserWorkItem也一样容易。

你不想阻止UI线程的任何操作都包装它,

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

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

我发现这允许我编写完全相同的代码,但不会阻止UI线程。如果要执行多个语句,也可以使用块。

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循环,因此不会干扰接口线程。再一次,它是相同的代码,只包含在lambada中。

    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 :(得分:1)

据我所知,这是不可能的。您只能在需要时运行和调用单个方法或在不同的线程上对它们进行排队。在单独的线程上设置实际对象会破坏您的目的。这是因为在单独的线程而不是对象上调用方法时,您只会利用多线程的好处。

然后将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);

答案 2 :(得分:1)

这不简单,但请看一下。这是一个很好的解释如何使用跨线程通信。

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

答案 3 :(得分:0)

到目前为止,这是我发现的(来自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");
    }
}