Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use delegates to pass methods in a thread wrapper class?

I'm currently self-teaching myself C# and I'm a bit new at programming so apologies in advance if this is covered in another topic (I tried searching).

I've been trying to make a generic worker / thread class that takes in a method which specifically wraps around a long set of procedural steps. The idea is to be able to pause/resume it a manner similar to setting breakpoints to pause/unpause in Visual Studio. To provide context, I'm mostly working with automation with an ASP.NET and XAML WPF interface (XAML at the moment).

My understanding is that I need to use delegates of some sort but I'm looking for a very simple example in plain English. The examples I found are a completely different scope and I have a hard time following the provided solutions in other contexts.

From other examples on MSDN and Stackoverflow, the "Task" worker class is what I have so far, but I'm a bit at a loss on where to on DoDelegatedMethod and my constructor. What I'm trying to do here is to instantiate a Task object, pass in a delegated method on new instantiation, create a new thread, and marry the passed in method to the thread.

The reason why I want a general "Task" is so I can manage specific methods generically instead of having to write a different "DoWork" method for each instance.

Is this the right approach?

class Task
{
    private ManualResetEvent _shutdownFlag = new ManualResetEvent(false);
    private ManualResetEvent _pauseFlag = new ManualResetEvent(true);
    private delegate void MyDelegate();

    Thread _thread;

    public Task() { }

    public Task(MyDelegate d = new MyDelegate(DoStuff)) // ERROR
    {
        _thread = new Thread(DoDelegatedMethod); // ERROR
    }

    public void Start()
    {
        _thread.Start();
    }

    public void Resume()
    {
        _pauseFlag.Set(); ;
    }

    public void Stop()
    {
        _shutdownFlag.Set();
        _pauseFlag.Set();
        _thread.Join();
    }

    private void DoDelegatedMethod(MyDelegate d)
    {
        do
        {
            d();
        }
        while (!_shutdownFlag.WaitOne(0));
    }

    // This does nothing but spin forever until I force it to stop
    public void Spin()
    {
        do
        {
            // MessageBox.Show("test");
            _pauseFlag.WaitOne(Timeout.Infinite);
        }
        while (!_shutdownFlag.WaitOne(0));
        //MessageBox.Show("thread over");
    }
}
like image 941
Wibble Avatar asked Feb 11 '11 20:02

Wibble


1 Answers

new Thread() takes a ThreadStart (or ParameterisedThreadStart) argument, and your DoDelegatedMethod callback doesn't have the right signature for ThreadStart. So you need to write something like this:

ThreadStart method = new ThreadStart(() => DoDelegatedMethod(d));
_thread = new Thread(method);

This creates an anonymous callback (the () => DoDelegatedMethod(d) bit) which when run will call DoDelegatedMethod with the delegate d (which is 'captured' by the anonmyous method). Now you pass this anonymous callback to the Thread constructor, so when the thread runs, it will call the anonymous callback, which will in turn call DoDelegatedMethod(d). Effectively the lambda adapts DoDelegatedMethod to the ThreadStart signature.

Another way to do this would be to change DoDelegatedMethod to take no arguments, and store d as a member field of the Task class which DoDelegateMethod would access.

Also, the reason you get an error on your constructor is that default values can only be of a limited set of types, and delegates aren't one of them (only types that are allowed in attributes, like int, long, string and Type are permitted). Use an overload instead:

public Task() : this(new MyDelegate(DoStuff)) { ... }
public Task(MyDelegate d) { ... }

Note you may still get an error if DoStuff is an instance method of Task -- it's not clear. Personally I think having a default delegate for Task to run is a bit of an odd design, so you may just want to get rid of the default constructor.


Following the discussion in the comments I thought it was worth summarising the suggested revisions to the Task class:

public class Task
{
  private readonly Action _action;
  // other members as before

  // default constructor removed

  public Task(Action action)
  {
    _action = action;
  }

  public void Start()
  {
    ThreadStart ts = new ThreadStart(DoDelegatedMethod);
    _thread = new Thread(ts);
    _thread.Start();
  }

  private void DoDelegatedMethod()
  {
    do
    {
      _action();
    }
    while (!_shutdownFlag.WaitOne(0));  
  }

  // other members as before
}

And the usage:

Task task = new Task(this.AutomatedTasks);
task.Start();

private void AutomatedTasks() { ... }
like image 172
itowlson Avatar answered Sep 28 '22 15:09

itowlson