0

My apologies, I am now out of the office so I don't have the exact code, but I would appreciate some advice on the following situation.

The situation

I have a winform application with one button. (Well actually several but it can be reduced to one). This button calls a series of asynchronous operations. Every time this button is called, the set of operations it calls is assigned a unique serial number. (this operations involved some HttpClient operation)

If I press the button again before the first set of operations is finished, an error is expected to be returned.

What I have

  static int serialNumber=0;

 private async void button_Clicked(object sender, EventArgs e)
    {   int old= serialNumber;
        serialNumber++;

       //Do some stuff
       //here and then

        var result= await callFirstOperation(serialNumber); 
        if(!result)
           {  serialNumber=old;
              return;
             }

       //Do some more stuff

        var result2= await callSecondOperation(serialNumber); 
         if(!result2)
           { serialNumber=old;
             return;
            }

        //Some more stuff and async operations   ....
       Trace.WriteLine("Fiuuu... finished!");  
    }

As you can see when the button is pressed a serial operation is used for the rest of asyn operations, but if one of them fail, the number is back to the old value.

This would not be a problem if I press the button and wait for the completion of everything, But what happens if I press the button twice, the second time before the first set of operations is finished.

I thing serial number will be affected (by the second call) which will affect also the following operations of the first set.

What is the correct way to implement the objective?

Can static variables be used safely somehow with async functions?


EDIT: I thought that not resetting the serial number would help but think about this

First Time button-> Serial Number:1

(First time) Operation1(1)

(First time) Operation2(1)

Then second time button-> Serial Number :2

(Second time) Operation 1(2) returns error

(First time) Operation3(2)<---WAIT! this should be 1 not 2!

14
  • 1
    as far as I understood, you want to inhibit the second pressing of the button before the entire algorithm has finished from the first time. Is that correct? Commented Jul 9, 2018 at 13:26
  • 1
    " the serial number has to be unique." but do they have to be consecutive? If not, then there's no need to "reset" the serial number. just increment it, and if the operation fails, you have a gap in the numbers. IF that is not a big problem it will save you a lot of hassle. Commented Jul 9, 2018 at 13:28
  • 1
    If it's just about throwing an error when someone presses the button a second time before the first run can finish I suggest you using a mutex or semaphore to detect if a run is still going. Commented Jul 9, 2018 at 13:33
  • 1
    a simple bool ButtonAlreadyPressed should suffice. Make it true in the beginning and false at the end. and perform the action only if(!ButtonAlreadyPressed){... do async stuff } Commented Jul 9, 2018 at 13:40
  • 1
    Why not give a visual feedback to the operator at the start of the procedure by disabling the button: ( (button)sender).Enabled = false; When finished, enable the button again. Or as Mong Zhu already said: remember that your procedure is started. If the same procedure is to be started again, show a MessageBox Commented Jul 9, 2018 at 13:53

1 Answer 1

2

Ok as far as I understand your problem is that you want to prevent your program from running the async operations twice at the same time.

To do this you can use a semaphore. This thread-safe structure works basically like a counter. Every time a thread enters it will count down. After it reaches 0 no more threads can enter and will have to wait.

Semaphore semaphore = new Semaphore(1, 1);

This semaphore will allow 1 thread to enter and will be initialized with a count of 1. To "enter" a semaphore protected chunk of code you call:

 semaphore.WaitOne()

This will decrement the counter by 1 and now prevent any task that also tries to call WaitOne to return from this call.

After this, you can be sure that only 1 task at a time will ever be in this section.

To open the semaphore for the next task you call:

  semaphore.Release();

For a better understanding I threw together a minimal example of this relating to your problem:

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

  Semaphore semaphore = new Semaphore(1, 1);

  private async void button1_Click(object sender, EventArgs e)
  {
    if (!semaphore.WaitOne(0))
    {
      MessageBox.Show("Long-running task hasn't finished yet!");

      return;
    }

    await LongRunningTask();

    semaphore.Release();
  }

  public async Task LongRunningTask()
  {
    await Task.Delay(5000);
    MessageBox.Show("Long running finished...");
  }
}

Special here is the call of WaitOne(0). The first parameter (0) specifies that every task that wants to enter but can't because of a task already running. It will wait 0 milliseconds before returning. The trick here is that if it was denied entry WaitOne will return false. So we now know that there is already a task running and can terminate the second task which is done by the return.

I would also advise you to show this behavior by visual feedback to your user. For example by disabling the button as long as the actions are running as suggested in the comments above.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.