Chaining Async Tasks?

bl0rpbl0rpbl0rpbl0rp Member ✭✭
edited June 28 in Xamarin.iOS

Background Info

I am trying to create a hearing test app. Essentially what I am trying to do is:

  • In the background, at semi-random intervals a sound is played at a certain decibel/frequency level
  • If the user presses an button while the sound is playing, this is a "hit" and certain logic is applied (e.g. moving to next frequency)
  • If the user does not press a button while the sound is playing this is a "miss", other logic is applied (e.g. playing the sound at a louder decibel level)

I won't go down into the mechanics of the hearing test, but a problem I'm having is that a user must get a "hit" on the same value twice in a row. A simple successful use case would be:

  1. Tone plays at [email protected], user does not click the button resulting in a "miss", sound is played louder [email protected], user clicks button while sound is playing resulting in a "hit". That is the first round.
  2. The app starts over: playing a tone at [email protected] User again "misses". Tone plays at [email protected] and user clicks the button, resulting in their second "hit". They have now hit twice in a row at the same decibel level for the same frequency. The test now continues on to the next frequency, e.g. [email protected]

Current State

I think I may be approaching this wrong. Basically what I'm doing, is I have this button:

    btnBeginHearingTestFLAT.TouchUpInside += async (sender, e) =>
        {
    ...
            await BtnBeginHearingTest_TouchUpInside(sender, e);
    ...
        };

The method for the button just loops over each ear and the frequencies:

    async private Task BtnBeginHearingTest_TouchUpInside(object sender, EventArgs e)
    {
        App.userIsTesting = true;

        int[] frequenciesToTest = { 1000, 500, 2000, 4000 };
        string[] earsToTest = { "Right", "Left" };

        // Test one ear at a time
        foreach(string ear in earsToTest)
        {
            // Loop over each frequency, then move to the next ear
            foreach (int testFrequency in frequenciesToTest)
            {
                Console.WriteLine("UIVCHearingTest:BtnBeginHearingTest_TouchUpInside - Testing: " + testFrequency + "Hz on " + ear);
                await TestFrequency(testFrequency, ear);
                this.progressCompletion.Progress += 0.1f;
            }
        }

        App.userIsTesting = false;
        PerformSegue("HearingTestSuccessSegue", (Foundation.NSObject)sender);

    }

Here is where I think I might start to go wrong. The code for TestFrequency (commented out stuff is other things I've tried that don't work):

    async private Task TestFrequency(int freqToTest, string LeftRight)
    {
        //await SetThreshold(freqToTest, LeftRight);    //OLD
        threshold1 = -99f;
        threshold2 = -98f;
        while (threshold1 != threshold2)
        {
            //await SetThreshold(1, freqToTest, LeftRight);

            //Func<Task> task1 = async () => { await SetThreshold(1, freqToTest, LeftRight).ConfigureAwait(false); };
            //task1().Wait();

            //var result = await Task.Run(async () => await SetThreshold(1, freqToTest, LeftRight));

            //await Task.Run(() => SetThreshold(1, freqToTest, LeftRight));


            await Task.Run(() =>
            {
                InvokeOnMainThread(() =>
                {
                    SetThreshold(1, freqToTest, LeftRight);
                });
            });


            //await SetThreshold(1, freqToTest, LeftRight).ConfigureAwait(false); /try if the below doesn't work

            if (threshold1 == threshold2)
            {
                break;
            }

            //await SetThreshold(2, freqToTest, LeftRight).ConfigureAwait(false);

            //Func<Task> task2 = async () => { await SetThreshold(2, freqToTest, LeftRight).ConfigureAwait(false); };
            //task2().Wait();

            //await Task.Run(() => SetThreshold(2, freqToTest, LeftRight));

            await Task.Run(() =>
            {
                InvokeOnMainThread(() =>
                {
                    SetThreshold(2, freqToTest, LeftRight);
                });
            });

            // do same as above if below doesnt work


        }

        // App then does some record keeping stuff, recording results to a SQLite database, etc.

    }

The SetThreshold stuff currently is this:

   private async Task SetThreshold(int thresholdNumber, int freqToTest, string LeftRight)
    {
        await Task.Factory.StartNew(() =>
        {
            return SetThresholdSync(thresholdNumber, freqToTest, LeftRight);
        });
    }

    //private float SetThresholdSync(int thresholdNumber, int freqToTest, string LeftRight)
    private async Task<float> SetThresholdSync(int thresholdNumber, int freqToTest, string LeftRight)
    {
        float setThreshold = -99.0f;
        int lastHitdBLevel = -100;

        //int dbLevel = 30;
        // Set the dbLevel to 30 to start, then carry it forward
        int dbLevel = prevFreqDbLevel;

        while (App.userIsTesting)
        {
            // Reset our variables
            didPressButton = false;
            soundIsPlaying = true;

            // Play the current sound
            await PlayRequestedSound(freqToTest, dbLevel, LeftRight);
            await Task.Delay(GetDelayVariation());

            soundIsPlaying = false;

            // Hit
            if (didPressButton)
            {
                Console.WriteLine("UIVCHearingTest:SetThreshold - HIT on " + freqToTest + "Hz " + dbLevel + "dB");
                lblTemp1.Text = "Correctly identified tone";
                didPressButton = false;

                // Some logic here for moving around to other levels, etc., not really important
            // When the right thing happens we break out                    

            }
            // Miss
            else
            {
                Console.WriteLine("UIVCHearingTest:SetThreshold - MISS on " + freqToTest + "Hz " + dbLevel + "dB");
                lblTemp1.Text = "Missed identifying tone";

                // Go up ("down" in technical speak) 5dB
                dbLevel = dbLevel + 5;

                if (dbLevel > 80)
                {
                    setThreshold = 80;
                    prevFreqDbLevel = 30;
                    return setThreshold;
                    break;
                }

            }

        }

        //userThreshold = setThreshold; //OLD
        if (thresholdNumber == 1)
        {
            threshold1 = setThreshold;
        }
        else if (thresholdNumber == 2)
        {
            threshold2 = setThreshold;
        }
        return setThreshold;
    }

The Problem / Question

So my question is: is my approach fundamentally flawed? Does anyone have some pointers on how I might implement this? I need some type of async so that I can track if the user pressed the button while the sound was playing, but I also need the 2 hits to happen in a row. Having the app be aware of the results and stop the test once that has happened is not working correctly.

I've been at this for a week now. ANY help would be GREATLY appreciated. If you need more code I'm happy to post it. I've tried to keep it to what I thought was relevant here

Answers

  • LandLuLandLu Member, Xamarin Team Xamurai
    await PlayRequestedSound(freqToTest, dbLevel, LeftRight);
    await Task.Delay(GetDelayVariation());
    
    soundIsPlaying = false;
    
    // Hit
    if (didPressButton)
    {
        // ...                  
    }
    

    The code in the if statement won't be triggered until the PlayRequestedSound finished. I don't know what PlayRequestedSound used for. If it plays your music then the logic code in your if statement won't fire at the same time the audio plays.

    I think you should check your audio's information in your button's click event. Define a static audio manager to manage the manipulation. Maybe like: await AudioManager.PlayRequestedSound(...). Then when the button's click event fired, get the information through this manager class:

    private void Button_Clicked(object sender, EventArgs e)
    {
        // AudioManager.Frequency
        // AudioManager.Decibel
    }
    

    This AudioManager contains all the audio information you needed and you can access them at any time you want.

  • bl0rpbl0rpbl0rpbl0rp Member ✭✭

    Sorry, I should have included PlayRequestedSound, it is probably important:

      async private Task PlayRequestedSound(int hz, int db, string LeftRight)
        {
            string wav_filename = GetWaveFilenameAndSetVolume(hz, db);
            //await GetWaveFilenameAndSetVolume(hz, db);
    
            if (wav_filename != "INVALID")
            {
                if (App.devMode)
                    lblSoundPlaying.Text = "🔊 " + hz + "Hz " + db + "dB " + LeftRight + " ear";
    
                await App.AudioManager.PlayAudioTask(wav_filename, LeftRight);
    
                if (App.devMode)
                    lblSoundPlaying.Text = "";
            }
        }
    

    And the App.AudioManager.PlayAudioTask code is:

        public Task<bool> PlayAudioTask(string fileName, string LeftRight)
        {
            var tcs = new TaskCompletionSource<bool>();
    
            // Any existing sound playing?
            if (soundEffect != null)
            {
                //Stop and dispose of any sound
                soundEffect.Stop();
                soundEffect.Dispose();
            }
    
            NSUrl url = new NSUrl("AudioAssets/" + fileName);
    
            soundEffect = AVAudioPlayer.FromUrl(url);
    
            soundEffect.Volume = App.db_start;
    
            if (LeftRight == "Left")
                soundEffect.Pan = -1.0f;
            else if (LeftRight == "Right")
                soundEffect.Pan = 1.0f;
            else
            {
                System.Diagnostics.Debug.WriteLine("HearingTestAudioManager:PlayAudioTask - WARNING! LEFTRIGHT WAS " + LeftRight + ". Playing center pan");
                soundEffect.Pan = 0.0f;
            }
    
            soundEffect.FinishedPlaying += (object sender, AVStatusEventArgs e) =>
            {
                System.Diagnostics.Debug.WriteLine("DONE PLAYING");
                soundEffect = null;
                tcs.SetResult(true);
            };
    
            soundEffect.NumberOfLoops = 0;
            System.Diagnostics.Debug.WriteLine("STARTED PLAYING");
            soundEffect.Play();
    
            /*
            System.Console.WriteLine("HearingTestAudioManager:PlayAudioTask - soundEffect.NumberOfChannels.ToString() - " + soundEffect.NumberOfChannels.ToString()); //1
            System.Console.WriteLine("HearingTestAudioManager:PlayAudioTask - soundEffect.Volume.ToString() - " + soundEffect.Volume.ToString()); //1
            System.Console.WriteLine("HearingTestAudioManager:PlayAudioTask - soundEffect.AveragePower(0).ToString() - " + soundEffect.AveragePower(0).ToString()); //-160
            System.Console.WriteLine("HearingTestAudioManager:PlayAudioTask - soundEffect.PeakPower(0).ToString() - " + soundEffect.PeakPower(0).ToString()); //-160
            */
    
            return tcs.Task;
        }
    

    I think I'm doing something similar to what you suggest, but am missing something somewhere...

  • LandLuLandLu Member, Xamarin Team Xamurai

    but am missing something somewhere

    What did you miss?

  • bl0rpbl0rpbl0rpbl0rp Member ✭✭

    I'm not sure. When it runs the whole thing finishes in a few seconds. The delay is ignored, and the user cannot click on the button

  • LandLuLandLu Member, Xamarin Team Xamurai

    @bl0rpbl0rp Can you make a sample to help me understand your issues more clearly? I need it to reproduce your issue on my side.

  • bl0rpbl0rpbl0rpbl0rp Member ✭✭

    @LandLu I've attached a copy of my issue

    The main functionality is in UIVCTestGame. There are several problems with the current code. I think they all have to do with me not using async correctly.

  • bl0rpbl0rpbl0rpbl0rp Member ✭✭
    edited August 7

    Updated version attached - shows the problem a little better in UIVCTestGame.cs

Sign In or Register to comment.