1

In my Windorm Form application, I have two buttons which a loop function will begin to do the work when button1 is pressed and stop executing after button2 is pressed. How could i do this to prevent my GUI from non-responsive. How could i insert command while(button2.clicked != true)

My code for button 1:

private async void EmoStart_Click_1(object sender, EventArgs e)
    {
        //var repeat = "true";
        string imageFilePath = "C:\\Users\\Administrator\\source\\repos\\FaceDetection\\FaceDetection\\test3.jpg";
       while (VoiceStart_Click_2 != "true")
       {
        var image = pictureBox1.Image;
        image = resizeImage(image, new Size(1209, 770));
        image.Save(imageFilePath);
        if (File.Exists(imageFilePath))
        {
            var Emo = await FaceEmotion.MakeAnalysisRequest(imageFilePath);
            if (Emo[0].FaceAttributes.Emotion.Anger >= 0.5)
            {
                EmoBox.Text = "Anger, Bad Driving Condition, Soft Music will be played";
            }
            else if (Emo[0].FaceAttributes.Emotion.Contempt >= 0.5)
            {
                EmoBox.Text = "Contempt, Bad Driving Condition, Soft Music will be played";
            }
            else if (Emo[0].FaceAttributes.Emotion.Disgust >= 0.5)
            {
                EmoBox.Text = "Disgust, Bad Driving Condition, Soft Music will be played";
            }
            else if (Emo[0].FaceAttributes.Emotion.Fear >= 0.5)
            {
                EmoBox.Text = "Fear, Bad Driving Condition, Soft Music will be played";
            }
            else if (Emo[0].FaceAttributes.Emotion.Happiness >= 0.5)
            {
                EmoBox.Text = "Happiness, Good Driving Condition";
            }
            else if (Emo[0].FaceAttributes.Emotion.Neutral >= 0.5)
            {
                EmoBox.Text = "Neutral, Good Driving Condition";
            }
            else if (Emo[0].FaceAttributes.Emotion.Sadness >= 0.5)
            {
                EmoBox.Text = "Sadness, Bad Driving Condition, Rock Music will be played";
            }
            else if (Emo[0].FaceAttributes.Emotion.Surprise >= 0.5)
            {
                EmoBox.Text = "Happiness, Bad Driving Condition, Soft Music will be played";
            }
            else
            {
                EmoBox.Text = "Stable Condition, Good Driving Condition";
            }
        }
    }

While for my button2 code:

private async void VoiceStart_Click_2(object sender, EventArgs e)
{
    string command = await Voice.RecognizeSpeechAsync();
    VoiceBox.Text = command;
}

Thank you!

Presi
  • 806
  • 10
  • 26
Jun Wong
  • 97
  • 6

4 Answers4

2

You have to run your loop in a separate thread. For example, you can run it asynchroniously. Something like this:

// start the loop
private async void button1_Click(object sender, EventArgs e)
{
    LoopStopped = false;
    await StartLoopAsync();
}

// put yor while loop here
private Task StartLoopAsync()
{
    return Task.Run(() =>
    {
        while (LoopStopped == false)
        {
            var date = DateTime.Now;
            System.Diagnostics.Debug.WriteLine(date);

        }
        System.Diagnostics.Debug.WriteLine("Thread stopped.");
    });
}

// stop the loop
private void button2_Click(object sender, EventArgs e)
{
    LoopStopped = true;
}

Where LoopStopped is global boolean variable.

VDN
  • 717
  • 4
  • 12
  • yeah it worked, i can repeat the loop when i pressed button1 and stop the loop for button2. However, when i pressed button1 and button 2 after a few interval in multiple time, it shows error. The error code is at my "Emo[0].faceattribute.emotion" with error "Index was out of range. Must be non-negative and less than the size of the collection." – Jun Wong Feb 17 '20 at 09:44
  • Probably face/emotion capturing not always succeed and Emo doesn't have any element within. So your `Emo[0]` points to an emotion that doesn't there. You should check whether Emo has any element before you get one. – Alb Feb 17 '20 at 10:16
  • why not making `private void button1_Click` also async and `await StartLoop();` inside? – Mong Zhu Feb 17 '20 at 10:37
  • if my loop function in button1 haven't finished running but i pressed the button 2, will it affect the output? Can i solve using the above method (put await loopfunction in button1)? – Jun Wong Feb 18 '20 at 04:24
  • @JunWong I am not sure if I understand the question. Before starting an iteration, the loop checks if it can start (LoopStopped == false). Then, if the iteration is started, it will always run till the end, no matter if you clicked button2 or not. And only after the iteration is finished, the loop will check again, if the new iteration can be started. – VDN Feb 18 '20 at 08:20
1

All the operations you put into the EmoStart_Click_1 is running synchronously, except:

FaceEmotion.MakeAnalysisRequest(imageFilePath)

thus the interface (UI) is frozen.

It is not enough to change the signature of the method to async as you did. You must tell the compiler, which other parts should be awaited. You want your whole while function async!

private async void EmoStart_Click_1(object sender, EventArgs e)
{
    EmoStart.Enabled = false;           //I assume EmoStart is the name of your button
    await Task.Factory.StartNew(Loop);
    EmoStart.Enabled = true;
}

private void Loop()                     //since this method doesn't have async in its signature "var Emo = await FaceEmotion.MakeAnalysisRequest(imageFilePath);" won't compile, so you should change to the synchronous equivalent "var Emo = FaceEmotion.MakeAnalysisRequest(imageFilePath).Result;" --> note that it won't block due to "Task.Factory.StartNew".
{
    string imageFilePath = "C:\\Users\\Administrator\\source\\repos\\FaceDetection\\FaceDetection\\test3.jpg";
    while (...)
    {
        // do your stuff
    }

Then you can decide how you want to cancel the while loop.

Option 1.: you could use a global, bool variable:

private bool emotionsShouldBeProcessed;

and you set it true in EmoStart_Click_1 and false like this:

private async void VoiceStart_Click_2(object sender, EventArgs e)
{
    VoiceStart.Enabled = false;
    emotionsShouldBeProcessed = false;
    // start and await voice stuff 
    VoiceStart.Enabled = true;
}

Option 2.: you could use a CancellationToken to track if cancel is necessary.

CancellationTokenSource cSource;

private async void EmoStart_Click_1(object sender, EventArgs e)
{
    EmoStart.Enabled = false;
    cSource = new CancellationTokenSource();
    await Task.Factory.StartNew(() => Loop(cSource.Token));
    EmoStart.Enabled = true;
}
private void Loop(CancellationToken cToken)
{
    string imageFilePath = "C:\\Users\\Administrator\\source\\repos\\FaceDetection\\FaceDetection\\test3.jpg";
    while (true)
    {
        if (cToken.IsCancellationRequested)
            break;
        // otherwise do your stuff
    }
    // some clean up here if necessary
}
private async void VoiceStart_Click_2(object sender, EventArgs e)
{
    VoiceStart.Enabled = false;
    cSource.Cancel();
    VoiceStart.Enabled = true;
}

So far so good! But your code will crash

It will crash whenever you want to set EmoBox.Text as this can happen only on the UI thread. To avoid this, you need to ask the UI thread to interrupt until Textbox/Label/etc operations are going on, like this:

this.Invoke((MethodInvoket)delegate
{
    EmoBox.Text = "...";
});

Edit:

I would also check the Emo array whether it is empty since face- and emotion recognitions not always succeeds! Thus, Emo[0] can result in "Index was out of range" exception. Following code makes sure it isn't empty:

var Emo = ...;
if (Emo.Length > 0)
{
    if (...)
        // use Emo[0]
    else if (...)
        // use Emo[0] differently
}

Let me know if there is anything unclear.

Alb
  • 412
  • 5
  • 12
  • Could i know whats the function of voicestart.enabled and emostart.enabled? Is it used to avoid the button being pressed when task is running? – Jun Wong Feb 17 '20 at 10:20
  • Exactly. You absolutely want to avoid a 2nd start of the recognition! – Alb Feb 17 '20 at 10:26
  • For solving "Index out of range", i should put a line of "if (Emo.Length > 0)" in between line "var Emo = await FaceEmotion.MakeAnalysisRequest(imageFilePath);" and line "if (Emo[0].FaceAttributes.Emotion.Anger >= 0.5)" ? This line is to ensure it will do the below function when there is something inside my Emo[array]? – Jun Wong Feb 18 '20 at 04:36
  • i can't add "Emo.Length", there does not have Length definition – Jun Wong Feb 18 '20 at 04:47
  • [Arrays have Length property](https://docs.microsoft.com/en-us/dotnet/api/system.array.length?view=netframework-4.8) maybe you need to write `using System;` to the top of your .cs file. Otherwise the object returned from `FaceEmotion.MakeAnalysisRequest` is not an Array: Do 2 things. **1:** check if there is a `Count` property (shouldn't be `Count()` function) **2:** type `Emo.` and **press ctrl + space after the dot** to see what's available and select one that gives info. e.g.: `.Succeed`, `.HasItems`, `.ItemCount`, `.IsEmotionCaptured`, `.Any()`. +1 (worst idea) maybe you can foreach *Emo*. – Alb Feb 18 '20 at 10:25
  • I do have this statement "if (Emo != null && Emo.Count > 0)" . Does it have the same effect like "Emo.Length > 0"? – Jun Wong Feb 19 '20 at 09:09
  • @JunWong Yes, it is perfect! – Alb Mar 06 '20 at 14:04
0

You can create a bool variable and make the loop while your variable is true.

So set the variable to true, when button1 is clicked.

Then your while would look like this:

while(myBoolVariable)

And when button2 is clicked you can change the value to false and the while will stop.

Presi
  • 806
  • 10
  • 26
  • if i pressed my button1, it will send only (myBoolVariable = true) once, how can i repeat the loop when only i pressed button1 once? – Jun Wong Feb 17 '20 at 09:20
  • 1
    @Presi It won't work. It will make GUI irresponsive. – Atk Feb 17 '20 at 09:22
  • yeah it worked, i can repeat the loop when i pressed button1 and stop the loop for button2. However, when i pressed button1 then button 2 and start pressed again button1, it shows error. The error code is at my "Emo[0].faceattribute.emotion" with error "Index was out of range. Must be non-negative and less than the size of the collection." – Jun Wong Feb 17 '20 at 09:34
  • @JunWong This is a problem with your list, maybe it hasn't any item in it. – Presi Feb 17 '20 at 09:46
  • but it only occur after a few times i pressed button1 and 2. Will it be the timing problem for the processing time as i switch too fast between button1 and button 2? – Jun Wong Feb 17 '20 at 09:50
0

You can use a global variable(bool is a good choice)

when VoiceStart_Click_2 change the variable

and do check the variable when EmoStart_Click_1 is clicked

if (variable==true)
{
    var Emo = await FaceEmotion.MakeAnalysisRequest(imageFilePath);
    if (Emo[0].FaceAttributes.Emotion.Anger >= 0.5)
    {
       EmoBox.Text = "Anger, Bad Driving Condition, Soft Music will be played";
    }
    ...

}
hung
  • 1