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

Я работаю над проектом C#, хочу сделать небольшую паузу около 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;
        }
    }
}

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

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

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

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

Запрашиваемая вниз голосов:

DoEvents

Предупреждение: Полное, Полное и Непростительно Буйное Программирование Barnyard:

// 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)

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

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

Вы можете осмотреться 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

Или, может быть, посмотреть вокруг класса Таймер.

Я вижу, как это работает с событиями или задачами (если вы не можете использовать 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);  
        }
    }
}

Плюсы:

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

Минусы:

  • Требуется рефакторинг вашего кода

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

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

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

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

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

Теперь, когда вы делаете что-то, что занимает некоторое время, используйте асинхронную версию функции, чтобы запустить процесс. Пока этот процесс запущен, вы можете делать другие вещи. Когда вам нужен результат, дождитесь выполнения задачи и получите пустое значение, если асинхронный возврат вернет 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.
}
  • Каждая функция, которая вызывает асинхронную функцию, должна быть объявлена ​​как асинхронная
  • каждая асинхронная функция возвращает Task вместо void и Task<TResult> вместо TResult
    • Есть только одно исключение: обработчик события может вернуть 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).

Эрик Липперт (спасибо Эрику!) Объяснил async-await следующим образом в Stackru - async/await - верно ли это понимание?

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

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

Наконец записка о вашей обработке исключений

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

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

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

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

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. Пожалуйста, исправьте меня, если я ошибаюсь.

Другие вопросы по тегам