Как сделать паузу в процедуре и после нее вернуть значение?

Я работаю над проектом С#, хочу сделать небольшую паузу около 2 секунд внутри процедуры.

На самом деле я пытался использовать Invoke, но, как вы знаете, мы не можем использовать его внутри класса в такой процедуре.

Вот мой код для более подробной информации:

public class GenerateFile
{

    public CSPFEnumration.ProcedureResult GenerateFaxFile(string Daftar_No, string Channelno, string NationalCode)
    {
        string script = string.Format(" DECLARE @RC INT " +
                                        " DECLARE @Daftar_No INT = '{0}' " +
                                        " DECLARE @hokm_type_code INT = 100 " +
                                        " DECLARE @Channelno INT = '{1}' " +
                                        " DECLARE @Id_No BIGINT = '{2}' " +
                                        " EXEC @rc = [dbo].[Hokm_with_type] @Daftar_No, @hokm_type_code, @Channelno, @Id_No ",
                                        Daftar_No,
                                        Channelno,
                                        NationalCode);
        try
        {
            IEnumerable<string> commandStrings = Regex.Split(script, @"^\s*GO\s*$",
                                                    RegexOptions.Multiline | RegexOptions.IgnoreCase);
            Connect();
            foreach (string commandString in commandStrings)
            {
                if (commandString.Trim() != "")
                {
                    using (var command = new SqlCommand(commandString, Connection))
                    {
                        command.ExecuteNonQuery();
                    }
                }
            }

            DisConnect();

            string FaxFilePath = InternalConstant.FaxFilePath + "\\" + string.Format("Lhokm{0}.tif", Channelno);


            // I want to make a pause in here without locking UI

            if (File.Exists(FaxFilePath))
                return CSPFEnumration.ProcedureResult.Success;
            else
                return CSPFEnumration.ProcedureResult.Error;

        }
        catch (Exception ex)
        {
            InternalDatabase.GetInstance.InsertToPensionOrganizationException(ex);
            return CSPFEnumration.ProcedureResult.Error;
        }
    }
}

Я тоже пробовал ждать, но не могу вернуть правильное значение. потому что в этой процедуре, если я использую ожидание, значение вернется до завершения ожидания.

Редактировать:

А также я не хочу использовать Thread.Sleep, потому что он заблокирует пользовательский интерфейс. Спасибо за любую помощь.


person Elahe    schedule 08.10.2015    source источник
comment
Почему бы вам не использовать темы и Thread.Sleep?   -  person MusicLovingIndianGirl    schedule 08.10.2015
comment
Очевидно, я мог бы сказать вам решение. Но с другой стороны мне интересно, почему вы хотите это сделать? Потому что просто «бездельничать» для меня нехорошо.   -  person ckruczek    schedule 08.10.2015
comment
@AishvaryaKarthik, потому что я не хочу блокировать пользовательский интерфейс   -  person Elahe    schedule 08.10.2015
comment
@ckruczek, потому что создание файла занимает несколько раз, и если я проверю его сразу после завершения сценария, файл не существует, и процедура вернет ошибку. но через несколько секунд файл будет сгенерирован.   -  person Elahe    schedule 08.10.2015
comment
Нет, этого не должно быть. Я не вижу генерации файлов. И большую часть времени операции ввода-вывода/операции блокируют поток, который они вызывают.   -  person ckruczek    schedule 08.10.2015
comment
@ckruczek файл будет сгенерирован с помощью сценария в sql, который я выполняю в своем коде.   -  person Elahe    schedule 08.10.2015
comment
Почему бы вам не разделить бизнес-логику? Создайте метод IsFileExist(), который проверяет наличие файла, таким образом вам не придется блокировать пользовательский интерфейс, и как только вы получите подтверждение, вызовите процедуру.   -  person Y.S    schedule 08.10.2015
comment
@ Y.S Я объяснил, что этот файл будет создан через несколько секунд. поэтому, если я проверю наличие файла сразу после завершения скрипта, я не могу его найти. поэтому я должен проверить существование файла через несколько секунд. поэтому мне нужна пауза.   -  person Elahe    schedule 08.10.2015
comment
Что я предлагаю, так это то, что вы проверяете, был ли файл сгенерирован столько раз, сколько вам нужно, выполняйте действия с пользовательским интерфейсом во время проверки, вы можете установить ограничение по времени для проверки существования файла, а не для вашей процедуры, которая является более тяжелой операцией.   -  person Y.S    schedule 08.10.2015
comment
И еще один подход - создать событие, которое запускается при создании файла, событие может вызывать вашу операцию. таким образом, вам не нужно делать никаких проверок.   -  person Y.S    schedule 08.10.2015
comment
@ Y.S Многократная проверка существования файла также блокирует пользовательский интерфейс. а по поводу создания события, это совершенно другое с моей текущей логикой в ​​этом проекте.   -  person Elahe    schedule 08.10.2015
comment
Удачи, приятель, я бы посоветовал снова использовать подход, основанный на событиях, если вы начнете ждать 2 секунды, на следующей неделе это будет 5 секунд из-за проблем с производительностью, и будет расти и расти ... вы в конечном итоге потеряете контроль над процесс. С подходом, основанным на событиях, вы можете оптимизировать свою логику, сделать ее лучше и эффективнее, ничего не меняя в основной логике. удачи еще раз.   -  person Y.S    schedule 08.10.2015
comment
В этом мало смысла, вы хотите, чтобы основной поток вошел в метод, затем подождите несколько секунд внутри метода, но не был заблокирован, и вы не можете использовать async/await, Tasks или Timer, и вы не можете изменить логику и использовать события?   -  person Fabjan    schedule 13.10.2015
comment
Не могли бы вы использовать FileSystemWatcher прослушивать, когда файл создается?   -  person Evil Dog Pie    schedule 13.10.2015
comment
Посмотрите ответ @FastAl ниже. Это решает вашу проблему в краткосрочной перспективе, но будьте осторожны! все остальные правы в своих возражениях. Здесь пахнет плохим кодом!   -  person Matt Thomas    schedule 17.10.2015


Ответы (7)


Используйте функцию асинхронного ожидания:

Отметьте свой метод как асинхронный.

Добавьте Task.Delay(2000) в качестве ожидаемой задачи.

 public async CSPFEnumration.ProcedureResult GenerateFaxFile(string Daftar_No, string Channelno, string NationalCode)
        {
                -----
                // I want to make a pause in here without locking UI
              await Task.Delay(2000);
                -----
        }
person Igoy    schedule 13.10.2015
comment
Метод async не может возвращать CSPFEnumration.ProcedureResult. - person Servy; 20.10.2015

Просьба о минусах:

DoEvents

Предупреждение: тотальное, полное и непростительно вопиющее программирование скотного двора:

// before call (disable the UI element that called this so it can't re-enter)
DateTime st = DateTime.Now();
while(DateTime.Now.Subtract(st).TotalSeconds<3)
    System.Windows.Forms.DoEvents();
// after call (re-enable UI element)

Это казалось бы работать. Никакой ответственности, если люди указывают пальцем и смеются.

Эй, ты спросил!

person FastAl    schedule 13.10.2015
comment
Учитывая ограничения ОП (похоже, они не в состоянии реорганизовать всю программу, чтобы использовать что-то лучшее), это лучший ответ, даже если это скотный двор :) - person Matt Thomas; 17.10.2015
comment
Прошу минусы и получаю плюсы! Действительно лучший ответ при всех этих ограничениях. - person Ivan Stoev; 18.10.2015
comment
Если OP не может изменить поведение управления вызовами, это единственное, что можно сделать. - person NicolaSysnet; 18.10.2015

Вы можете посмотреть вокруг Task.Delay(), он не будет блокировать текущий поток и продолжит выполнение через несколько миллисекунд.

Пример использования из msdn:

Stopwatch sw = Stopwatch.StartNew();
var delay = Task.Delay(1000).ContinueWith(_ =>
                           { sw.Stop();
                             return sw.ElapsedMilliseconds; } );

Console.WriteLine("Elapsed milliseconds: {0}", delay.Result);
// The example displays output like the following:
//        Elapsed milliseconds: 1013

Или, возможно, просмотрите Таймер класс.

person Mikhail Tulubaev    schedule 08.10.2015
comment
Доза «Задача» не содержит определения «Задержка» - person Elahe; 08.10.2015
comment
если ваш Task равен System.Threading.Tasks.Task, он должен содержать этого члена - person Mikhail Tulubaev; 08.10.2015
comment
Task.Delay был представлен в .NET framework 4.5, поэтому, если вы используете более раннюю версию, у вас его не будет. - person Y.S; 08.10.2015
comment
@MikhailNeofitov Если я изменю свою платформу .NET на 4.5, моя проблема не будет решена. потому что, если я использую ваш код, значение вернется до ожидания 1 секунды, как вы определили в своем коде. - person Elahe; 08.10.2015
comment
@Elahe, поскольку вы ожидаете в другом потоке - вам нужно использовать delay.Wait(), чтобы быть уверенным, что задача будет завершена до завершения метода. - person Mikhail Tulubaev; 08.10.2015
comment
На самом деле Timer должен делать то, что вам нужно, не блокируя текущий поток. Вы можете посмотреть пример использования в msdn по ссылке в моем ответе. - person Mikhail Tulubaev; 08.10.2015
comment
@MikhailNeofitov Если я использую dely.Wait(), пользовательский интерфейс заблокируется, поэтому нет разницы между этим и Thread.Sleep! - person Elahe; 08.10.2015
comment
@Elahe в процедуре паузы вы глобально имеете в виду, что хотите приостановить поток, в котором выполняется эта процедура. Таким образом, вы можете запустить свою процедуру в другом потоке и использовать Thread.Sleep() или использовать Timer для создания другого потока и получения результатов в функции обратного вызова, а не в текущей процедуре. - person Mikhail Tulubaev; 08.10.2015
comment
@Elahe, поскольку ваша процедура выполняется в потоке пользовательского интерфейса, вы не можете просто приостановить выполнение потока, не блокируя выполнение потока пользовательского интерфейса. Вам нужно создать отдельный поток, в котором вы хотите выполнить выполнение после паузы и выполнить паузу в этом потоке, а затем синхронизировать поток пользовательского интерфейса и ваш отдельный поток. - person Mikhail Tulubaev; 08.10.2015
comment
Я пытался использовать Timer, но он также заблокировал пользовательский интерфейс... Использование отдельного потока полностью изменит мою логику. Спасибо за внимание. @МихаилНеофитов - person Elahe; 08.10.2015

Я вижу, как он работает с событиями или задачами (если вы не можете использовать async/await). Вот как создать событие. Мы можем использовать отдельный Thread, чтобы проверить, создан ли файл, и запустить событие, если это так:

public class FileGenEventArgs : EventArgs
{
    public string ProcedureResult { get; set; }
}

public class GenerateFile
{
    public event EventHandler<FileGenEventArgs > fileCreated;

    public GenerateFile()
    {
        // subscribe for this event somewhere in your code.
        fileCreated += GenerateFile_fileCreated;
    }

    void GenerateFile_fileCreated(object sender, FileGenEventArgs args)
    {            
        // .. do something with  args.ProcedureResult
    }

    private void FileCheck()
    {
        Thread.Sleep(2000); // delay

        fileCreated(this, new FileGenEventArgs()
        {
            ProcedureResult = File.Exists(FaxFilePath) ?
              CSPFEnumration.ProcedureResult.Success :
                 CSPFEnumration.ProcedureResult.Error
        });            
    }

    public void GenerateFaxFile(string Daftar_No, string Channelno, string NationalCode)
    {
        try
        {
            // this .Sleep() represents your sql operation so change it
            Thread.Sleep(1000);

            new Thread(FileCheck).Start();
        }
        catch (Exception ex)
        {
            InternalDatabase.GetInstance.InsertToPensionOrganizationException(ex);  
        }
    }
}

Плюсы:

  • Пауза, которую вы хотели
  • Не блокирует поток пользовательского интерфейса.
  • Подход, основанный на событиях (который является правильным способом решения проблем такого рода)

Минусы:

  • Требуется рефакторинг вашего кода
person Fabjan    schedule 13.10.2015
comment
Это единственно правильное решение! Вы не можете заблокировать пользовательский интерфейс — › вы не можете приостановить вызовы из пользовательского интерфейса — вам нужно использовать фоновый рабочий. - person Tomas Kubes; 19.10.2015

Самый простой способ подождать, сохраняя отзывчивость пользовательского интерфейса, — это использовать async-await.

Для этого вы должны объявить свою функцию асинхронной и вернуть Task вместо void и Task<TResult> вместо TResult:

public async Task<CSPFEnumration.ProcedureResult> GenerateFaxFile(
    string Daftar_No,
    string Channelno,
    string NationalCode)
{
    // do your stuff,
}

Теперь всякий раз, когда вы делаете что-то, что требует некоторого времени, используйте асинхронную версию функции для запуска процесса. Пока идет этот процесс, вы можете заниматься другими делами. Когда вам нужен результат await для задачи, и вы получаете пустоту, если асинхронность возвращает Task, или TResult, если асинхронность возвращает Task<TResult>

public async Task<CSPFEnumration.ProcedureResult> GenerateFaxFile(
    string Daftar_No,
    string Channelno,
    string NationalCode)
{
    IEnumerable<string> commandStrings = Regex.Split(
        script, @"^\s*GO\s*$", RegexOptions.Multiline | RegexOptions.IgnoreCase);

    Connect();
    foreach (var commandString in commandStrings)
     {
        if (commandString.Trim() != "")
        {
            using (var command = new SqlCommand(commandString, Connection))
                {
                    Task<int> task = command.ExecuteNonQueryAsync();
                    // while the command is being executed
                    // you can do other things.
                    // when you need the result: await
                    int result = await task;
                    // if useful: interpret result;
                }
            }
        }

        DisConnect();
        ... etc.
}
  • Каждая функция, которая вызывает асинхронную функцию, должна быть объявлена ​​асинхронной.
  • every async function returns Task instead of void and Task<TResult> instead of TResult
    • There is only one exception: the event handler may return void.

Пример обработчика асинхронного события:

private async void OnButton1_Clicked(object sender, ...)
{
    var task = GenerateFaxFile(...);
    // while the fax file is generated do some other stuff
    // when you need the result:
    var procedureResult = await task;
    Process(procedureResult);
}

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

Вышеупомянутого достаточно, чтобы ваш пользовательский интерфейс оставался отзывчивым. Вы сказали, что хотите знать, как подождать какое-то время. Из остальной части вашего вопроса я понимаю, что вы имели в виду: как прервать процедуру, пока она чего-то ждет, чтобы пользовательский интерфейс мог делать что-то другое. Если вам действительно нужно подождать некоторое время, сохраняя отзывчивость пользовательского интерфейса, используйте Task.Delay(TimeSpan).

Эрик Липперт (спасибо, Эрик!) объяснил асинхронное ожидание следующим образом в ">Stackoverflow - async/await - правильно ли это понимание?

Предположим, на завтрак вам нужно поджарить хлеб и сварить яйца. Для этого есть несколько сценариев:

  1. Начните поджаривать хлеб. Подождите, пока это не закончится. Начинайте варить яйца, дождитесь окончания. Синхронная обработка. Пока вы ждете, пока поджарится хлеб, вы больше ничего не можете делать.
  2. Начните поджаривать хлеб, пока хлеб поджаривается, начните готовить яйца. когда яйца будут приготовлены, подождите, пока хлеб не поджарится. Это называется асинхронным, но не одновременным. Это делается основным потоком, и пока этот поток что-то делает, основной поток не может делать ничего другого. Но пока он ждет, у него есть время заняться другими делами (например, заварить чай)
  3. Наймите поваров, чтобы поджарить хлеб и приготовить яйца. Подождите, пока оба не закончатся. Асинхронный и параллельный: работа выполняется разными потоками. Это самое дорогое, потому что вам нужно начинать новые потоки.

Наконец, примечание об обработке исключений

Вы заметили, что если возникает исключение, вы не отключаетесь? Правильный способ убедиться, что разъединение всегда вызывается, заключается в следующем:

try
{
    Connect();
    ... do other stuff
}
catch (Exception exc)
{
     ... process exception
}
finally
{
    Disconnect();
}

Наконец, часть всегда выполняется, независимо от того, было ли выброшено какое-либо исключение или нет.

person Harald Coppoolse    schedule 13.10.2015
comment
Это ваш лучший выбор. Ваш метод GenerateFaxFile должен ожидаться из вашего пользовательского интерфейса. Это выполнит GenerateFaxFile в отдельном потоке и приостановит работу до тех пор, пока метод не вернется БЕЗ блокировки пользовательского интерфейса. Затем вы можете просто использовать Thread.Sleep или какой-либо другой метод блокировки, чтобы подделать 2-секундную задержку. С учетом сказанного, ваш оригинальный подход - это запах кода. Задержка в 2 секунды не гарантирует, что файл готов. Это должна быть двухэтапная процедура. 1) Выполните работу по созданию файла 2) Подождите, пока файл будет готов (с тайм-аутом) и поднимите четное значение, если время ожидания истекло, верните ошибку - person Tom; 13.10.2015
comment
Не лучше ли использовать await Task.Delay(TimeSpan) вместо Thread.Sleep(TimeSpan)? По крайней мере, это сохранит отзывчивость пользовательского интерфейса. - person Harald Coppoolse; 14.10.2015
comment
Нет необходимости ждать Task.Delay, если весь процесс выполняется в отдельном потоке. Пока он ждет описанного вами метода, он может поместить туда Thread.Sleep. С учетом сказанного, он определенно может также ожидать Task.Delay, но это приводит к обработке другой задачи и, следовательно, может вызвать дополнительные накладные расходы, поскольку она, вероятно, будет использовать другой поток из ThreadPool. - person Tom; 14.10.2015
comment
Я знаю, что вы можете использовать Thread.Sleep. Я просто хотел показать, насколько просто асинхронное ожидание по сравнению с запуском System.ComponentModler.BackGroundWorker, где вы должны сообщать о ходе выполнения, выполнять некоторые сложные действия, отправляя параметры и сообщая результат. Если вы используете System.Threading.Tasks.Task, вам придется проделать некоторые трюки с InvokeRequired, прежде чем вы сможете взаимодействовать с пользовательским интерфейсом. Все эти неприятности не возникают при использовании async-await - person Harald Coppoolse; 14.10.2015
comment
Я думаю, что мы на одной волне. Определенно асинхронно ожидайте основного метода (GenerateFaxFile), чтобы пользовательский интерфейс реагировал, и процесс происходил в ThreadPool. Моя единственная точка зрения на Thread.Sleep заключалась в том, что теперь он может безопасно использовать его, если выберет ваш подход, поскольку метод уже выполняется в отдельном потоке. - person Tom; 14.10.2015

Вы можете использовать простой пул потоков для архивирования этого. Однако ваш возврат должен выполняться асинхронно, чтобы он не блокировал графический интерфейс.

public void GenerateFaxFile(string Daftar_No, string Channelno,
            string NationalCode, Action<CSPFEnumration.ProcedureResult> result)
        {
            ThreadPool.QueueUserWorkItem(o =>
            {
                string script = "your script";
                try
                {
                    // more of your script

                    // I want to make a pause in here without locking UI
                    while (true)
                    {
                        // do your check here to unpause
                        if (stopMe == true)
                        {
                            break;
                        }

                        Thread.Sleep(500);
                    }


                    if (File.Exists(FaxFilePath))
                    {
                        result(CSPFEnumration.ProcedureResult.Success);
                        return;
                    }
                    else
                    {
                        result(CSPFEnumration.ProcedureResult.Error);
                    }


                }
                catch (Exception ex)
                {
                    InternalDatabase.GetInstance.InsertToPensionOrganizationException(ex);
                    result(CSPFEnumration.ProcedureResult.Error);
                    return;
                }

            });

        }

        public void HowToUseMe()
        {
            GenerateFaxFile("", "", "", result => {

                if (result == CSPFEnumration.ProcedureResult.Error)
                {
                    // no good
                }
                else
                {
                    // bonus time
                }
            });
        }
person Du D.    schedule 19.10.2015

Вы должны использовать старый добрый фоновый поток (см. ответ, написанный FabJan), или вы можете использовать async и await с контекстом синхронизации:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void buttonStart_Click(object sender, EventArgs e)
    {
        await progressBar1.DoProgress(2000);
        Trace.WriteLine("Done");          
        MessageBox.Show("Done");
    }

    private void buttonMoveButton1_Click(object sender, EventArgs e)
    {
        //to prove UI click several times buttonMove while the task is ruunning
        buttonStart.Top += 10;
    }
}

public static class WaitExtensions
{
    public static async Task DoProgress(this ProgressBar progressBar, int sleepTimeMiliseconds)
    {
        int sleepInterval = 50;
        int progressSteps = sleepTimeMiliseconds / sleepInterval; //every 50ms feedback
        progressBar.Maximum = progressSteps;

        SynchronizationContext synchronizationContext = SynchronizationContext.Current;
        await Task.Run(() =>
        {
            synchronizationContext.OperationStarted();

            for (int i = 0; i <= progressSteps; i++)
            {
                Thread.Sleep(sleepInterval);
                synchronizationContext.Post(new SendOrPostCallback(o =>
                {
                    Trace.WriteLine((int)o + "%");

                    progressBar.Value = (int)o;
                }), i);
            }

            synchronizationContext.OperationCompleted();
        });
    }
}    

Может показаться, что MessageBox сделал шоу до того, как ProgressBar достигнет своего максимума. Я виню в этом волшебную анимацию progressBar в Windows 8. Пожалуйста, поправьте меня, если я ошибаюсь.

person Tomas Kubes    schedule 19.10.2015