How to make a pause in a procedure and then return value after it?
Asked Answered
I

7

13

I'm working on a C# project, want to make a small pause about 2 seconds inside a procedure.

Actually I have tried to use Invoke, but as you know, we can't use it inside a class this kind of procedure.

Here is my code for more details:

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

I have tried await too, but I cant return a proper value. because in this procedure if I use await, the value will return before finishing await.

Edit:

And also I dont want to use Thread.Sleep because it will lock UI. Thanks for any helping.

Importunacy answered 8/10, 2015 at 5:18 Comment(15)
Why don't you use threads and Thread.Sleep?Westley
Clearly I could tell you a solution. But on the other hand I am interessted why you want to do this? Because just 'iddling' around smells not good for me.Cyrenaica
@AishvaryaKarthik because I dont want Lock in UIImportunacy
@Cyrenaica because generating file with take some times and if I check it exactly after finishing script, the file is not exist and the procedure will return Error. but after some seconds the file will generate.Importunacy
No this shouldnt happen. I don't see any file generation. And most of the time, IO/Operations are blocking the thread they are called.Cyrenaica
@Cyrenaica the file will generate with a script in sql that I execute that in my code.Importunacy
Why don't you split the business logic? Create a method IsFileExist() which checks for the existence of the file, this way you won't have to lock the UI, and once you get the confirmation, call the Procedure.Violaviolable
@Violaviolable I explained that this file will generate after some seconds. so If I check for file existence, exactly after finishing script, I cant find it. so I have to ckeck file existence after some seconds. so I need a pause.Importunacy
What i'm suggesting, is that you check if the file has been generated as many times as you like, Do UI stuff while your'e checking, you can set the time limit for the file existence check, and not for your procedure which is the heavier operation.Violaviolable
And another approach, is to create an event which is triggered when a file is generate, the event can invoke your operation. this way you don't need to do any checks.Violaviolable
@Violaviolable check file existence for many times will Lock the UI too. and about creating an event, Its completely different with my current logic in this project.Importunacy
Good luck buddy, I would suggest again to take the "event based approach", if you start waiting 2 seconds, the next week it will be 5 seconds because of performance issues, and will grow and grow... you will eventually lose control of the process. With event based approach, you can optimize your logic, make it better, more efficient without changing anything in the core logic. good luck again.Violaviolable
This makes little sense, you want main thread to enter method, then wait a few seconds inside the method but not been blocked and you cannot use async/await nor Tasks or Timer, and you cannot change logic and use events ?Underfoot
Could you use a FileSystemWatcher to listen for when the file is created?Synchroscope
Check out @FastAl's answer below. It solves your problem in the short-term, but Buyer-Beware! everyone else is right in their objections. There are some bad code smells here!Brigid
M
7

Use async await feature :

Mark your method as async .

Add Task.Delay(2000) as the waited task.

 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);
                -----
        }
Mcgrew answered 13/10, 2015 at 6:16 Comment(1)
An async method cannot return CSPFEnumration.ProcedureResult.Antiphrasis
L
2

Asking for downvotes:

DoEvents

Warning: Total, Complete and Inexcusably Flagrant Barnyard Programming:

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

This will appear to work. No responsibility if people point and laugh.

Hey, you asked!

Laddie answered 13/10, 2015 at 20:37 Comment(3)
Given the constraints by the OP (it seems like they aren't in a position to refactor the whole program to use something better), this is the best answer even though it's "barnyard" :)Brigid
Asking for downvotes and getting upvote! Indeed the best answer under all these constraints.Sural
If the OP can't change the calling control behaviour, this is the only thing to do.Voyeur
U
1

You can look around Task.Delay() it will not block current thread and continue execution after number of milliseconds.

Exmaple usage from 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

Or maybe look around Timer class.

Uphroe answered 8/10, 2015 at 5:37 Comment(11)
'Task' dose not contain a definition for 'Delay'Importunacy
if your Task is System.Threading.Tasks.Task it should contain this memberUphroe
Task.Delay was introduced in .NET framework 4.5, So if youre using a lower version you won't have it.Violaviolable
#15342462Violaviolable
@MikhailNeofitov If I change my .NET framework to 4.5, my problem will not solve. because If I use your code, The the value will return before waiting for 1 second as u define in your code.Importunacy
@Importunacy as your waiting in another thread - you need to use delay.Wait() to be sure that task will be ended before method end.Uphroe
Actually, Timer should do what you need, without blocking current thread. You can look example usage in msdn by link at my answer.Uphroe
@MikhailNeofitov If I use dely.Wait() the UI will Lock, so not difference between this and Thread.Sleep!Importunacy
@Importunacy in pause procedure you globally mean that you want to pause thread in what running this procedure. So, you can run your procedure in another thread and use Thread.Sleep() or use Timer to create another thread and take results in callback function, not in current procedureUphroe
@Importunacy as your procedure running in UI thread, you can not simply pause thread execution without blocking UI thread execution. You need to create separate thread where you want to perform execution after pause and perform pause in that thread, then sync UI thread and your separate threadUphroe
I have tried to use Timer but It locked the UI too... Using seperate thread will change my logic totally. Thanks for your attention. @MikhailNeofitovImportunacy
U
0

I can see it working with events or Tasks (if you cannot use async / await). This is how to create event. We can use separate Thread to check if file is created and fire event if it is:

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

Pros :

  • Pause that you wanted
  • Doesn't block the UI thread.
  • Event-based approach (which is proper way of dealing with this kind of problems)

Cons :

  • Requires to refactor your code
Underfoot answered 13/10, 2015 at 7:43 Comment(1)
This is the only correct solution! You can't lock UI -> you cannot pause anything called from UI -> you need to use background worker.Sun
W
0

The most easy thing to wait while keeping the UI responsive is using async-await.

To do this, you must declare your function async, and return Task instead of void and Task<TResult> instead of TResult:

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

Now whenever you do something that takes some time, use the async version of the function to start the process. While this process is running, you can do other stuff. When you need the result await for the task, and you get the void if the async returns Task, or the TResult if the async returns 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 function that calls an async function should be declared async
  • 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.

Example of async event handler:

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

Note that everything is processed by the UI thread. The only difference is that as soon as anything time consuming happens, the process doesn't have a busy wait, but processes UI input.

The above is enough to keep your UI responsive. You said you wanted to know how to wait some time. From the rest of your question I understand that you meant: how to interrupt the procedure while it is waiting for something, so the UI can do other thing. If you really need to wait some time while keeping the UI responsive, use Task.Delay(TimeSpan).

Eric Lippert (thanx Eric!) explained async-await as follows in Stackoverflow - async/await - Is this understanding correct?

Suppose for breakfast you have to toast bread and cook eggs. There are several scenarios for it:

  1. Start toasting bread. Wait until it is finished. Start cooking eggs, wait until it is finished. Synchronous processing. While you are waiting for the bread to toast you can't do anything else.
  2. Start toasting bread, while the bread is being toasted start cooking eggs. when the eggs are cooked wait until the bread finished toasting. This is called Asynchronous, but not concurrent. It is done by the main thread and as long as this thread does something, the main thread can't do anything else. But while it is waiting it has time to do other things (make some tea for instance)
  3. Hire cooks to toast the bread and cook the eggs. Wait until both are finished. Asynchronous and concurrent: the work is done by different threads. This is the most expensive because you have to start new threads.

Finally a note about your exception handling

Do you notice that if an exception occurs you don't disconnect?. The proper way to make sure that disconnect is always called is the following:

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

The finally part is always executed, regardless of any exception being thrown or not.

Wheelock answered 13/10, 2015 at 8:22 Comment(5)
This is your best bet. Your GenerateFaxFile method should be await'd from your UI. This will execute GenerateFaxFile on a separate thread and "pause" until the method returns WITHOUT blocking the UI. Then you can simply use Thread.Sleep or some other blocking method to fake a 2 seconds delay. With that said, your original approach is a code smell. The 2 second delay will not guarantee that the file is ready. This should be a 2 step procedure. 1) Do work to generate file 2) Wait for file to be ready (with a timeout) and raise an even, if timeout expires return ErrorPuce
Isn't it better to use await Task.Delay(TimeSpan) instead of Thread.Sleep(TimeSpan)? At least it will keep the UI responsiveWheelock
There is no need to await a Task.Delay if the entire process is being done on a separate thread. As long as he awaits the method you described, he can put a Thread.Sleep in there. With that said, he can definitely also await a Task.Delay but that is causing another Task to be processed and therefore may cause more overhead since it will probably use another Thread from the ThreadPool.Puce
I know you can use Thread.Sleep. I just wanted to show how easy async-await is compared to starting a System.ComponentModler.BackGroundWorker, where you have to report progress, do some difficult things sending parameters and communicating the result. If you use System.Threading.Tasks.Task, you have to do some tricks with InvokeRequired before you can communicate with the UI. All those nuisances do not occur when using async-awaitWheelock
I think we are on the same page. Definitely async-await the main method (GenerateFaxFile) so the UI is responsive and the process occurs on a ThreadPool. My only point of Thread.Sleep was that he can now safely use it if he chooses your approach since the method is already being executed on a separate thread.Puce
E
0

You can use simple Thread Pool to archive this. However your return has to do asynchronously so it doesn't lockup the 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
                }
            });
        }
Epimorphosis answered 19/10, 2015 at 20:33 Comment(0)
S
0

You should use the old good background thread (see answer written by FabJan) or you can use 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();
        });
    }
}    

It could appear that MessageBox done Shows before the ProgressBar is on its Maximum. I blame for this magic animation of progressBar in Windows 8. Please correct me if I am wrong.

Sun answered 19/10, 2015 at 20:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.