0

Sorry for my bad English. Hope someone suggests me a better version of my question.

I've searched but seemed like I couldn't find the answer for my problem.

Currently, I'm writing a C# WPF app. This app will perform a heavy task in a long time. So I've decided to create another class with that heavy method and pass that method to another thread. I have to create a class to do that because the heavy method takes parameters.

I want the ability to suspend and resume that thread. I've known that I should use a ManualResetEvent object or Thread.Sleep method.

After many hours of trying and testing, getting confused why I always end up suspend the UI thread but the heavy thread is still running. What I've tried were:

  1. Create a ManualResetEvent object called mre inside the HeavyClass. When user click the Pause button, the UI class will call the method heavyClass.mre.WaitOne().

    class HeavyClass
    {
        // properties
        ManualResetEvent mre = new ManualResetEvent(false);
    
        public void HeavyRun()
        {
    
            //Do something takes really long time
            //And doesn't have any loops
        }
    }
    
    class MainWindow : Window
    {
        // properties
        private HeavyClass heavyClass = new HeavyClass();
    
        private void buttonStart_Click(object sender, RoutedEventArgs e)
        {
            Thread t = new Thread(heavyClass.HeavyRun);
            t.Start();
        }
    
        private void buttonPause_Click(object sender, RoutedEventArgs e)
        {
            heavyClass.mre.WaitOne();
        }
    }
    
  2. Create a method called SleepThread inside the HeavyClass. When user click the Pause button, the UI class will call the method heavyClass.SleepThread().

    class HeavyClass
    {
        //properties
        ManualResetEvent mre = new ManualResetEvent(false);
    
        public void SleepThread()
        {
            Thread.Sleep(Timeout.Infinite);
            //mre.WaitOne();
            //They are the same behavior
        }
    
        public void HeavyRun()
        {
            //Do something takes really long time
            //And doesn't have any loops
        }
    }
    
    class MainWindow : Window
    {
        // properties
        private HeavyClass heavyClass = new HeavyClass();
    
        private void buttonStart_Click(object sender, RoutedEventArgs e)
        {
            Thread t = new Thread(heavyClass.HeavyRun);
            t.Start();
        }
    
        private void buttonPause_Click(object sender, RoutedEventArgs e)
        {
            heavyClass.SleepThread();
        }
    }
    
  3. Create an EventHandler<MainWindow> PauseThread inside the UI class, then write its handle inside the HeavyClass. When user click the Pause button, the UI class will trigger the event PauseThread(this, this).

    class MainWindow : Window
    {
        // properties
        private HeavyClass heavyClass = new HeavyClass();
        public event EventHandler<MainWindow> PauseThread;
    
        private void buttonStart_Click(object sender, RoutedEventArgs e)
        {
            Thread t = new Thread(heavyClass.HeavyRun);
            t.Start();
        }
    
        private void buttonPause_Click(object sender, RoutedEventArgs e)
        {
            PauseThread(this, this);
        }
    }
    
    class HeavyClass
    {
        // properties
        ManualResetEvent mre = new ManualResetEvent(false);
    
        public void HeavyRun()
        {
            MainWindow.PauseThread += (s, E) => 
            {
                Thread.Sleep(Timeout.Infinite);
                //mre.WaitOne();
                //They are the same behavior
            };
            //Do something takes really long time
            //And doesn't have any loops
        }
    }
    

As I said above, I always paused the UI thread and the heavy task is still running.

And finally in the end, I've known the essence of my problem. That is: which thread calls Thread.Sleep() or WaitOne() will be blocked. Yeah, "which thread", not "which class".

Everything makes sense for me now. But that doesn't help me to achieve my goal. And that leads me to think that I am doing the seemingly impossible thing. It's clearly that I want to pause a thread by another thread. But that another thread is the one who calls any kinds of "suspend thread", so it is the one who is suspended. I don't have any idea about how to make the heavy method to be suspended by itself. It is running, how the hell it could know when the user click the Pause button?

I am at a total loss. Someone please help me to make my app works as expected.

By the way, this impossible thing makes me think that I am doing things wrong way, is it?

UPDATE: If you like to see my heavy task, actually it is very simple

    class HeavyClass
    {
        public string filePath = "D:\\Desktop\\bigfile.iso";//This file is about 10GB

        public string HeavyRun()
        {
            string MD5Hash;
            MD5 md5 = MD5.Create();
            Stream stream = File.OpenRead(filePath);
            MD5Hash = Encoding.Default.GetString(md5.ComputeHash(stream));
            return MD5Hash;
        }
    }
Presto
  • 888
  • 12
  • 30
peanut
  • 133
  • 3
  • 11
  • You're sleeping the UI thread because the event will be called on the UI thread. – MineR Jul 02 '18 at 05:59
  • Instead, set a flag in the PauseThread event, watch for it on the Heavy thread, and sleep when it's true. – MineR Jul 02 '18 at 06:00
  • I also thought about setting a flag approach. But the Heavy method doesn't have any loops, so I don't know how to watch the flag. Am I right? – peanut Jul 02 '18 at 06:04
  • So why's it taking a long time? – MineR Jul 02 '18 at 06:31
  • For example, it is calculating the checksum of a very very big file. The method that has the responsibility to compute hash is not mine. I just write 1 line of code and it will take long time to finish. – peanut Jul 02 '18 at 06:35
  • You can get a ref to the heavy thread and do https://msdn.microsoft.com/en-us/library/system.threading.thread.suspend(v=vs.110).aspx... but read the warning, and see if that's what you really want to do. You aren't meant to get control of threads in the way you describe.You're not even supposed to call Thread.Abort, unless the program is ending too. – MineR Jul 02 '18 at 06:56
  • "I have to create a class to do that because the heavy method takes parameters." - No, that's not true. What makes you think that? – Enigmativity Jul 02 '18 at 06:58
  • You should not be trying to do anything with another thread unless it is co-operative behaviour. You can't force one thread to behave from another. It would really help if you could show us what the heavy calculation was. – Enigmativity Jul 02 '18 at 07:01
  • As the docs of microsoft [here](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/threading/parameters-and-return-values-for-multithreaded-procedures) that said "The best way to supply parameters for a multithreaded method call is to wrap the target method in a class and define fields for that class that will serve as parameters for the new thread." – peanut Jul 02 '18 at 07:02
  • @Enigmativity I've just updated my question, you can see that method now. – peanut Jul 02 '18 at 07:11
  • `Stream stream = File.OpenRead(filePath);` this line is a one-way street to a locked file and a memory leak – TheGeneral Jul 02 '18 at 07:15
  • OK, I know what you mean. So we should not try to force another thread behavior, that not good at all. – peanut Jul 02 '18 at 07:31
  • @peanut - You only have a few options. You can't forcibly pause the processing of the hash. You can (1) let it run, (2) put it in its own process and still just let it run, or (3) change the computation of the hash to build it in chunks using the `MD5.ComputeHash(byte[], int, int)` signature (which you could then pause as needed). The last option will compute a different hash to processing the whole file, but as long as you are consistent in producing the hash there shouldn't be a problem. – Enigmativity Jul 02 '18 at 08:22
  • @peanut - Also, you should keep in mind that it is very common to compute a hash only for the first several MB of a file. Let's say you have two files that are the same length and have the same hash for the first 10MB of the file then you could probably then commit to computing the full hash if need be. – Enigmativity Jul 02 '18 at 08:25

1 Answers1

0

To make a thread suspendable, the work in the thread must be separable. In your case md5.ComputeHash(stream) will do all the work, and there is not way to make sure that thread will suspend at a right(saft) point inside md5.ComputeHash(stream). So you have to rewrite HeavyClass like below. Please notice that those codes are not the best approach of handling a thread, and I just try to keep it as same as the original.

class HeavyClass
{
    MD5 _md5 = MD5.Create();
    MethodInfo _hashCoreMI = _md5.GetType().GetMethod("HashCore", BindingFlags.NonPublic | BindingFlags.Instance);
    MethodInfo _HashFinalMI = _md5.GetType().GetMethod("HashFinal", BindingFlags.NonPublic | BindingFlags.Instance);
    WaitHandle _signal;

    public void HeavyClass(WaitHandle signal)
    {
        _signal = signal;
    }

    public string HeavyRun(string filename)
    {
        byte[] buffer = new byte[4096];
        int bytesRead = 0;
        _signal.Set();

        using(FileStream fs = File.OpenRead(filename))
        {
            while(true)
            {
                bytesRead = fs.Read(buffer, 0, 4096);
                if (bytesRead > 0)
                {
                    _hashCoreMI.Invoke(_md5, new object[] { buffer, 0, bytesRead });
                }
                else
                {
                    break;
                }

                // if WaitHandle is signalled, thread will be block,
                // otherwise thread will keep running.
                _signal.WaitOne();
            }
        }

        byte[] hash = _hashFinalMI.Invoke(_md5, null);
        _md5.Initialize();

        return Encoding.ASCII.GetString(hash);;
    }
}

class MainWindow : Window
{
    private HeavyClass _heavyClass;
    private ManualResetEvent _mre;

    public MainWindow()
    {
        InitializeComponent();

        _mre = new ManualResetEvent(true);
        _heavyClass = new HeavyClass(_mer);
    }

    private void buttonStart_Click(object sender, RoutedEventArgs e)
    {
        Thread t = new Thread(heavyClass.HeavyRun("D:\\Desktop\\bigfile.iso"));
        t.Start();
    }

    private void buttonPause_Click(object sender, RoutedEventArgs e)
    {
        _mre.Reset();
    }

    private void buttonResume_Click(object sender, RoutedEventArgs e)
    {
        _mre.Set();
    }
}
Alex.Wei
  • 1,798
  • 6
  • 12