15

I want my program to wait after below line

frmProgressBarObj = PullMSI.ExtractByMSIName("products.txt", false);

as above method is internally calling thread through StartProcessWithProgress() method . I want that thread to be completed before //code logic -2 line gets executed. At the same time, It should not stop UI update done by frmProgressBar.UpdateProgress(). How do I do this?

namespace NS1
{
    public partial class frmMain : Form
    {                
        private void button1_Click(object sender, EventArgs e)
        {
            frmProgressBar frmProgressBarObj = PullMSI.ExtractByMSIName("products.txt", false);
            //code logic - 2
            MessageBox.Show("This is executing immediately. 
                             I want to wait until above thread is complete");
        }
    }

    public partial class frmProgressBar : Form
    {

        public void UpdateProgress(String strTextToDisplayOnProgress)
        {
            progressBar1.BeginInvoke(
                   new Action(() => 
                   { 
                       progressBar1.Value++; 
                       lblFileName.Text = strTextToDisplayOnProgress;
                       if (progressBar1.Value == progressBar1.Maximum)
                       {
                           this.Hide(); 
                        } 
                    }));
        }

        public delegate void DelProgress();

        public void StartProcessWithProgress(DelProgress delMethodCode, int maxCount)
        {
            InitializeProgress(maxCount);
            Thread backgroundThread = new Thread(new ThreadStart(delMethodCode));
            backgroundThread.Start();
        }
    }

    public static class PullMSI
    {
        public static frmProgressBar ExtractByMSIName(String strProductFilePath, bool reNameMSI)
        {
            frmProgressBar frmProgressBar = new frmProgressBar();

            frmProgressBar.StartProcessWithProgress(() =>
            {
                //StreamRader sr declaration and other code

                while (!sr.EndOfStream)
                {
                    //logic here
                    frmProgressBar.UpdateProgress("Copying sr.msiname");
                }
            }, 2);

            return frmProgressBar;
        }
    }
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Ashif Nataliya
  • 912
  • 2
  • 13
  • 28
  • 1
    UI? This is a console application. – Yuval Itzchakov Jul 01 '15 at 12:01
  • 3
    My bad. Sorry, In order to simplify code, I just put all classes in console application so that I can easily post here. But its Windows form application and process start on button click. – Ashif Nataliya Jul 01 '15 at 12:16
  • 1
    You can use either of these: 1) TPL with task continuation 2) Reset events (ManualResetEventSlim/AutoResetEventSlim) 3) Using other mechanism such as Semaphore (highly discourage your from doing this) 4) async/await if you're running .Net 4.5+. 5) Producer/Consumer (overkill for your use-case). Basically there are a variety of ways this is possible. Do a read up on these and pick one that you like most. – kha Jul 01 '15 at 12:16
  • Actually I have not used these techniques before and it will take time for me to learn. Client expectation is haunting me :( any code sample will be great help. – Ashif Nataliya Jul 01 '15 at 12:19
  • You must not wait in a UI event-handler - it's a state-machine and must service its input queue promptly. Bocking waits with events/semaphores are not useable at all. Message signalling systems like Invoke/BeginInvoke are saner. – Martin James Jul 01 '15 at 16:21

2 Answers2

38

Below are three different ways you can achieve what you want:

1. Using reset events (further reading). If your C# version doesn't have the ManualResetEventSlim, replace it with ManualResetEvent and change Wait() with WaitOne()

class LockingWithResetEvents
{
    private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false);

    public void Test()
    {
        MethodUsingResetEvents();
    }

    private void MethodUsingResetEvents()
    {
        ThreadPool.QueueUserWorkItem(_ => DoSomethingLong());
        ThreadPool.QueueUserWorkItem(_ => ShowMessageBox());
    }

    private void DoSomethingLong()
    {
        Console.WriteLine("Doing something.");
        Thread.Sleep(1000);
        _resetEvent.Set();
    }

    private void ShowMessageBox()
    {
        _resetEvent.WaitOne();
        Console.WriteLine("Hello world.");
    }
}

2) Using Task Parallel Library (TPL). (Further reading)

class LockingWithTPL
{
    public void Test()
    {
        Task.Factory.StartNew(DoSomethingLong).ContinueWith(result => ShowMessageBox());
    }

    private void DoSomethingLong()
    {
        Console.WriteLine("Doing something.");
        Thread.Sleep(1000);
    }

    private void ShowMessageBox()
    {
        Console.WriteLine("Hello world.");
    }
}

3) Using Async/Await. (Further reading)

class LockingWithAwait
{
    public void Test()
    {
        DoSomething();
    }

    private async void DoSomething()
    {
        await Task.Run(() => DoSomethingLong());
        ShowMessageBox();
    }

    private async void DoSomethingLong()
    {
        Console.WriteLine("Doing something.");
        Thread.Sleep(10000);
    }

    private void ShowMessageBox()
    {
        Console.WriteLine("Hello world.");
    }
}

Also good to know: Mutex, Semaphore, lock, SemaphoreSlim, Monitor and Interlocked.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
kha
  • 19,123
  • 9
  • 34
  • 67
  • Doesn't your `Test` method need to be declared `Async` also? I thought that all methods that call Async methods had to also be Async. Async/Await is still a little new to me, so I may have that wrong. – Bradley Uffner Jul 01 '15 at 12:59
  • @BradleyUffner Doesn't have to. I'm not `await`ing anything. If you're not awaiting it, it doesn't need the `async` keyword and will instead treat it as a simple method with void return type. This however would not compile: `await DoSomething();`. – kha Jul 01 '15 at 13:02
  • NO, no, no waiting in UI event-handlers! – Martin James Jul 01 '15 at 16:19
  • @MartinJames Was that meant for me? none of the three solutions I have posted will block the calling thread I believe. – kha Jul 02 '15 at 04:04
2

If you're using .NET 4.0 (with VS2012) or above, you can do this quite easily with the Task Parallel Library and async-await:

private async void button1_Click(object sender, EventArgs e)
{
    frmProgressBar frmProgressBarObj = await Task.Run(() =>
                      PullMSI.ExtractByMSIName("products.txt", false));

    MessageBox.Show(string.Format("Returned {0}", frmProgressBarObj.ToString());
}

For .NET 4, you'll need to add Microsoft.Bcl.Async.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321