Как приостановить поток другим потоком в C #? - PullRequest
0 голосов
/ 02 июля 2018

Извините за мой плохой английский. Надеюсь, кто-то предложит мне лучшую версию моего вопроса.

Я искал, но казалось, что не могу найти ответ на мою проблему.

В настоящее время я пишу приложение на C # WPF. Это приложение будет выполнять тяжелую задачу в течение длительного времени. Поэтому я решил создать другой класс с этим тяжелым методом и передать этот метод другому потоку. Я должен создать класс, чтобы сделать это, потому что тяжелый метод принимает параметры.

Я хочу приостановить и возобновить эту тему. Я знал, что должен использовать объект ManualResetEvent или метод Thread.Sleep.

После многих часов попыток и тестирования я запутался, почему я всегда заканчиваю тем, что приостанавливаю поток пользовательского интерфейса, но тяжелый поток все еще работает. То, что я пробовал, было:

  1. Создайте ManualResetEvent объект с именем mre внутри HeavyClass. Когда пользователь нажимает кнопку «Пауза», класс пользовательского интерфейса вызовет метод 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. Создайте метод с именем SleepThread внутри HeavyClass. Когда пользователь нажимает кнопку «Пауза», класс пользовательского интерфейса вызовет метод 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. Создайте EventHandler<MainWindow> PauseThread внутри класса пользовательского интерфейса, затем напишите его дескриптор внутри HeavyClass. Когда пользователь нажимает кнопку «Пауза», класс пользовательского интерфейса вызовет событие 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
        }
    }
    

Как я уже говорил выше, я всегда приостанавливал поток пользовательского интерфейса, и тяжелая задача все еще выполняется.

И, наконец, в конце концов, я понял суть моей проблемы. То есть: какой поток вызывает Thread.Sleep () или WaitOne () будет заблокирован. Да, «какая нить», а не «какой класс».

Теперь все для меня имеет смысл. Но это не помогает мне достичь своей цели. И это заставляет меня думать, что я делаю, казалось бы, невозможную вещь. Ясно, что я хочу приостановить поток другим потоком. Но этот другой поток является тем, кто называет любые виды «приостановить поток», так что это тот, кто приостановлен. Я не имею ни малейшего представления о том, как сделать тяжелый метод сам по себе приостановленным. Он работает, как, черт возьми, он мог знать, когда пользователь нажимает кнопку Пауза?

Я в полной растерянности. Кто-то, пожалуйста, помогите мне заставить мое приложение работать как ожидалось.

Кстати, эта невозможная вещь заставляет меня думать, что я делаю вещи неправильно, не так ли?

ОБНОВЛЕНИЕ: Если вам нравится видеть мою тяжелую задачу, на самом деле это очень просто

    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;
        }
    }

1 Ответ

0 голосов
/ 04 июля 2018

Чтобы сделать поток приостановленным, работа в потоке должна быть отделимой. В вашем случае md5.ComputeHash(stream) выполнит всю работу, и нет никакого способа убедиться, что нить приостановится в правой (saft) точке внутри md5.ComputeHash(stream). Таким образом, вы должны переписать HeavyClass, как показано ниже. Обратите внимание, что эти коды не лучший подход к обработке потока, и я просто стараюсь сохранить его таким же, как и в оригинале.

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();
    }
}
...