【问题标题】:How to make a pause in a procedure and then return value after it?如何在过程中暂停,然后在它之后返回值?
【发布时间】:2026-01-10 06:50:02
【问题描述】:

我正在处理一个 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,值会在等待完成之前返回。

编辑:

而且我也不想使用Thread.Sleep,因为它会锁定 UI。 感谢您的帮助。

【问题讨论】:

  • 为什么不用线程和Thread.Sleep
  • 显然我可以告诉你一个解决方案。但另一方面,我很感兴趣你为什么要这样做?因为只是“闲逛”对我不利。
  • 为什么不拆分业务逻辑呢?创建一个方法 IsFileExist() 来检查文件是否存在,这样你就不必锁定 UI,一旦你得到确认,就调用过程。
  • 另一种方法是创建一个事件,在生成文件时触发,该事件可以调用您的操作。这样你就不需要做任何检查了。
  • 祝你好运哥们,我再次建议采取“基于事件的方法”,如果你开始等待2秒,下周会因为性能问题而等待5秒,并且会越来越长...您最终将失去对过程的控制。使用基于事件的方法,您可以优化逻辑,使其更好、更高效,而无需更改核心逻辑中的任何内容。再次祝你好运。

标签: c# pause


【解决方案1】:

使用异步等待功能:

将您的方法标记为异步。

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

【讨论】:

  • 一个async方法不能返回CSPFEnumration.ProcedureResult
【解决方案2】:

要求投反对票:

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)

这将看起来起作用。有人指指点笑,不负责任。

嘿,你问了!

【讨论】:

  • 鉴于 OP 的限制(似乎他们无法重构整个程序以使用更好的东西),这是最好的答案,即使它是“谷仓”:)
  • 要求反对票并获得赞成票!确实是所有这些限制条件下的最佳答案。
  • 如果 OP 不能改变调用控制行为,这是唯一要做的事情。
【解决方案3】:

你可以看看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

或者看看Timer类。

【讨论】:

  • “任务”不包含“延迟”的定义
  • 如果你的TaskSystem.Threading.Tasks.Task 它应该包含这个成员
  • @MikhailNeofitov 如果我使用 dely.Wait() 用户界面将锁定,所以这和 Thread.Sleep 没有区别!
  • @Elahe 作为您在 UI 线程中运行的过程,您不能简单地暂停线程执行而不阻塞 UI 线程执行。您需要创建单独的线程,在暂停后执行执行并在该线程中执行暂停,然后同步 UI 线程和您的单独线程
【解决方案4】:

我可以看到它与事件或任务一起工作(如果你不能使用异步/等待)。这是创建事件的方法。我们可以使用单独的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);  
        }
    }
}

优点:

  • 您想要的暂停
  • 不阻塞 UI 线程。
  • 基于事件的方法(这是处理此类问题的正确方法)

缺点:

  • 需要重构您的代码

【讨论】:

  • 这是唯一正确的解决方案!你不能锁定 UI -> 你不能暂停从 UI 调用的任何东西 -> 你需要使用后台工作者。
【解决方案5】:

在保持 UI 响应时最容易等待的是使用 async-await。

为此,您必须声明您的函数异步,并返回 Task 而不是 void 和 Task&lt;TResult> 而不是 TResult:

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

现在,每当您执行需要一些时间的操作时,请使用该函数的异步版本来启动该过程。在此过程运行时,您可以执行其他操作。当您需要等待任务的结果时,如果异步返回任务,您将获得 void,或者如果异步返回任务,您将获得 TResult&lt;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&lt;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);
}

请注意,所有内容都由 UI 线程处理。唯一的区别是,只要发生任何耗时的事情,该进程就不会等待,而是处理 UI 输入。

以上内容足以让您的 UI 保持响应。你说你想知道如何等待一段时间。从你的问题的其余部分我明白你的意思是:如何在等待某事时中断程序,以便 UI 可以做其他事情。如果您确实需要等待一段时间以保持 UI 响应,请使用 Task.Delay(TimeSpan)。

Eric Lippert (thanx Eric!) 在 * - async/await - Is this understanding correct? 中解释 async-await 如下

假设早餐你必须烤面包和煮鸡蛋。有几种情况:

  1. 开始烤面包。等到它完成。开始煮鸡蛋,等到它完成。同步处理。在等待面包烤好时,您无能为力。
  2. 开始烤面包,在烤面包的同时开始煮鸡蛋。当鸡蛋煮熟时,等到面包烤完。这称为异步,但不是并发的。它是由主线程完成的,只要这个线程做某事,主线程就不能做任何其他事情。但在它等待的时候,它有时间做其他事情(例如泡茶)
  3. 聘请厨师烤面包和煮鸡蛋。等两个都完成。异步和并发:工作由不同的线程完成。这是最昂贵的,因为您必须启动新线程。

最后是关于您的异常处理的说明

您是否注意到如果发生异常您不会断开连接?确保始终调用断开连接的正确方法如下:

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

finally 部分总是被执行,无论是否抛出任何异常。

【讨论】:

  • 这是您最好的选择。您的 GenerateFaxFile 方法应该在您的 UI 中等待。这将在单独的线程上执行 GenerateFaxFile 并“暂停”,直到该方法返回而不阻塞 UI。然后您可以简单地使用 Thread.Sleep 或其他一些阻塞方法来伪造 2 秒的延迟。话虽如此,您最初的方法是代码异味。 2 秒的延迟并不能保证文件已准备就绪。这应该是一个两步程序。 1)做工作以生成文件 2)等待文件准备好(超时)并引发一个偶数,如果超时到期返回错误
  • 用 await Task.Delay(TimeSpan) 代替 Thread.Sleep(TimeSpan) 不是更好吗?至少它会保持 UI 响应
  • 如果整个过程在单独的线程上完成,则无需等待 Task.Delay。只要他等待您描述的方法,他就可以在其中放置一个 Thread.Sleep 。话虽如此,他当然也可以等待 Task.Delay ,但这会导致另一个任务被处理,因此可能会导致更多开销,因为它可能会使用 ThreadPool 中的另一个线程。
  • 我知道你可以使用 Thread.Sleep。我只是想展示 async-await 与启动 System.ComponentModler.BackGroundWorker 相比有多么简单,您必须在其中报告进度,执行一些困难的事情,发送参数并传达结果。如果使用 System.Threading.Tasks.Task,则必须先使用 InvokeRequired 进行一些技巧,然后才能与 UI 进行通信。使用 async-await 时不会发生所有这些麻烦
  • 我认为我们是一致的。绝对是异步等待主要方法(GenerateFaxFile),因此 UI 是响应式的,并且该过程发生在 ThreadPool 上。我对 Thread.Sleep 的唯一看法是,如果他选择您的方法,他现在可以安全地使用它,因为该方法已经在单独的线程上执行。
【解决方案6】:

您可以使用简单的线程池来归档它。但是,您的返回必须异步执行,因此它不会锁定 gui。

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

【讨论】:

    【解决方案7】:

    你应该使用旧的好后台线程(参见FabJan写的答案)或者你可以使用async and await with synchronization context

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

    在 ProgressBar 达到最大值之前,MessageBox 可能会显示已完成。我为 Windows 8 中 progressBar 的这种神奇动画负责。如果我错了,请纠正我。

    【讨论】:

      最近更新 更多