Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Prevent Thread Re-entrance on Same Thread 2

Status
Not open for further replies.

sammye

Programmer
Oct 5, 2007
35
US
I am looking for a way to prevent reentrance on the same thread. The problem is that I have a static function that is called from a form in a couple of ways (button, timer), and I have been using a lock to prevent reentrance from other threads and that portion seems to be working fine. However, I believe that asynchronous events on the same thread are causing me trouble.

Any suggestions? Is this a good place for a semaphore? I am not very familiar with using semaphores, mutex's, etc, so I am looking for a pointer in the right direction. All I know is that lock doesn't cover what I need. Thanks.

Joe
 
Good advice, thanks for all the tips/info. I'll keep digging and post back *when* (staying positive here and staying away from "if") I find the root of the problem. Thanks again.

joe
 
I got this working. here is the code within the user controls to handle the threading stuff
Code:
public partial class FirstUserControl : UserControl
{
    public FirstUserControl()
    {
        InitializeComponent();
    }

    public void DisplayCurrentStats(object sender, PollingEventArgs args)
    {
        var statistics = args.Statisics;
        var message = string.Format("{0} Iteration {1:000} took place at {2: MM-dd-yy HH:mm:ss}", statistics.Text, statistics.Number, statistics.OccuredAt);

        Preform
            .ThisAction(() => CurrentStats.Text = message)
            .Against(this);
    }
}
Code:
public partial class SecondUserControl : UserControl
{
    private readonly List<PollingStatistics> HistoryOfStatistics;

    public SecondUserControl()
    {
        HistoryOfStatistics = new List<PollingStatistics>();
        InitializeComponent();
    }

    public void DisplayHistoryOfStats(object sender, PollingEventArgs args)
    {
        HistoryOfStatistics.Add(args.Statisics);
        Preform
            .ThisAction(() => History.Text = BuildMessage())
            .Against(this);
    }

    private string BuildMessage()
    {
        var builder = new StringBuilder();
        HistoryOfStatistics.ForEach(stat => builder.AppendLine(string.Format("{0} Iteration {1:000} took place at {2: MM-dd-yy HH:mm:ss}", stat.Text, stat.Number, stat.OccuredAt)));

        return builder.ToString();
    }
}
and here is the Preform API I created to invoke the action.
Code:
public static class Preform
{
    public static SomthingBuilder ThisAction(Action action)
    {
        return new SomthingBuilder(action);
    }
}

public class SomthingBuilder
{
    private delegate void Now();
    private readonly Action action;

    public SomthingBuilder(Action action)
    {
        this.action = action;
    }

    public void Against(ISynchronizeInvoke control)
    {
        if (control.InvokeRequired)
        {
            control.Invoke(Do.This(action), null);
        }
        else
        {
            action();
        }
    }

    private class Do
    {
        private readonly Action action;

        public static Now This(Action action)
        {
            return new Do(action).execute;
        }

        private Do(Action action)
        {
            this.action = action;
        }

        private void execute()
        {
            action();
        }
    }
}

Jason Meckley
Programmer
Specialty Bakers, Inc.
 
Wow, still looking at this, huh? Thanks for the follow-up, I'll look through the code you posted.
 
This post has been on my mind for some time and I finally got around to another spike. this time I'm using a SynchronizationContext to manage cross threading issues. According to Udi, SynchronizationContext are preferred over ISynchronizeInvoke when large portions of the GUI are updated (sorry no link). Jeremy Miller provided some insight into how to use the SynchronizationContext

This example code I have is to update the form 5 after it's loaded. the 5 second delay is a simple Thread.Sleep(5000). The UI remains responsive as this can be tested by clicking the button.
Code:
public interface IView
{
    void UpdateUI(string value);
}
public partial class MainForm : Form, IView
{
    private readonly Presenter Presenter;

    public MainForm()
    {
        InitializeComponent();
        Presenter = new Presenter(this, new Service(), new AsynchronousExecutor(SynchronizationContext.Current));
    }

    protected override void OnShown(EventArgs e)
    {
        Presenter.UpdateView();
    }

    public void UpdateUI(string value)
    {
        CurrentTimeLabel.Text = value;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show("hello", "caption", MessageBoxButtons.OK);
    }
}
Code:
public class Presenter
{
    private readonly IView View;
    private readonly IService Service;
    private readonly ICommandExecutor Executor;

    public Presenter(IView view, IService service, ICommandExecutor executor)
    {
        View = view;
        Service = service;
        Executor = executor;
    }

    public virtual void UpdateView()
    {
        Executor.Execute(() =>
                             {
                                 //all the work that should happen on the background thread should start here
                                 var text = Service.GetData();
                                 //and end here

                                 //return an action to update the GUI
                                 return () => View.UpdateUI(text);
                             });
    }
}
Code:
public interface IService
{
    string GetData();
}

public class Service : IService
{
    private const int FIVE_SECONDS = 5000;

    public string GetData()
    {
        Thread.Sleep(FIVE_SECONDS);
        return string.Format("the time is {0:F}", DateTime.Now);
    }
}
Code:
public interface ICommandExecutor
{
    void Execute(Action action);
    void Execute(Func<Action> action);
}

public class AsynchronousExecutor : ICommandExecutor
{
    private readonly SynchronizationContext synchronizationContext;

    public AsynchronousExecutor(SynchronizationContext synchronizationContext)
    {
        this.synchronizationContext = synchronizationContext;
    }

    public void Execute(Action action)
    {
        ThreadPool.QueueUserWorkItem(item => action());
    }

    public void Execute(Func<Action> action)
    {
        ThreadPool.QueueUserWorkItem(item =>
                                         {
                                             var continuation = action();
                                             synchronizationContext.Send(callBack => continuation(), null);
                                         });
    }
}
the MainForm, IView, Presenter and Service are standard MVP objects that break out responsiblities. Nothing to fancy there. The AsynchronousExecutor is where all the real work is done. here is the break down:
1. ThreadPool.QueueUserWorkItem is a helper class to push work to background threads.
2. void Execute(Action action) is used for calls that would not update the GUI.
3. Execute(Func<Action> action) is used when the GUI needs to be updated. the idea is to push as much work into a background thread. Once you are ready to push updates to the UI you can synchronize the threads.

I like this approach more than my original spike. It's cleaner and easier to test.

Jason Meckley
Programmer
Specialty Bakers, Inc.

faq855-7190
 
I like this - very simple and easy to understand compared to most other examples on the web.

Good job Jason!
 
I admit, I need to read through this a couple of times before I understand it all. Thanks again, I appreciate the education.
 
I created faq732-7259 which summarizes the two approaches in this thread. I also found a 3rd approach using the Event Aggregator Pattern. An example of this can be found in the source code of StoryTeller. The code is very elegant for such a complex problem. Complex, but not complicated.

Jason Meckley
Programmer
Specialty Bakers, Inc.

faq855-7190
faq732-7259
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top