Search Unity

Question Running operations in the background and keeping the game responsive

Discussion in 'Scripting' started by slowDrag0n, Jun 9, 2020.

  1. slowDrag0n

    slowDrag0n

    Joined:
    Oct 10, 2017
    Posts:
    41
    Hi

    I am using NAudio Plugin (https://github.com/WulfMarius/NAudio-Unity) to take mp3 files from certain directory, covert them to wav format and store them in streaming assets folder.

    Code (CSharp):
    1. public void loadFiles()
    2. {
    3.     DirectoryInfo inputDirectory = new DirectoryInfo(Application.streamingAssetsPath + @"\mp3\");
    4.     DirectoryInfo outputDirectory = new DirectoryInfo(Application.streamingAssetsPath + @"\wav\");
    5.     if (!outputDirectory.Exists)
    6.         outputDirectory.Create();
    7.     Debug.Log("Directory Path: " + inputDirectory.FullName);
    8.  
    9.     FileInfo[] allFiles = inputDirectory.GetFiles("*.mp3");
    10.  
    11.     foreach (FileInfo file in allFiles)
    12.     {
    13.         string outputFile = outputDirectory.FullName + Path.GetFileNameWithoutExtension(outputDirectory.FullName + file.Name) + "_wav.wav";
    14.         Debug.Log("Output: " + outputFile);
    15.  
    16.         using (Mp3FileReader mp3Reader = new Mp3FileReader(inputDirectory.FullName + file.Name))
    17.         {
    18.             WaveFileWriter.CreateWaveFile(outputFilePath, mp3Reader);
    19.         }
    20.     }
    21. }
    When i call this method the game freezes completely and stays totally unresponsive until all files are finished converting.

    Is is possible to do run process in the background. What i want to do is run this process completely in the background and call a method passing converted file's path as parameter after completing conversion of every single file.

    I have looked at a few solutions like this one (https://answers.unity.com/questions/170089/using-asyncoperation-for-our-own-services.html) as well as async task eg (http://www.stevevermeulen.com/index.php/2017/09/using-async-await-in-unity3d-2017/) but i can't wrap my head around as to how i would implement this in my requirement.
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,911
    The simplest way I've found to do something in a background thread in C# is Task.Run: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netcore-3.1

    So for your example you could do this:

    Code (CSharp):
    1. var mp3Task = Task.Run(() => { loadFiles(); });
    That would run your code in a background thread. But the tricky part will be getting results back to your main thread somehow. One way to do it would be to poll the task object by checking IsCompleted in a Coroutine or Update method.
     
  3. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    Yep, you want threads! Threads can be run without having any impact on the game. The biggest challenges with threads are:
    1) Communicating safely between the side thread and the main thread
    2) Using most Unity classes in the thread is impossible

    The bad news is that #2 is very hard to work around, but the good news in your case is that it doesn't seem like you'll barely have to worry about that at all! (Except perhaps for Debug.Log - I don't remember whether that's one that can be used in threads or not)

    So then you just need to figure out how to get whatever data back from the thread. In your case this, again, probably won't be too difficult, because it looks like you won't have complicated data being shared between both. You'll probably want some sort of progress indicator I assume, in which case you can be safe with that by being very explicit about when that variable is used (e.g. only SET it from the side thread and only READ it from the main thread).

    The simplest form of threaded code looks like this:
    Code (csharp):
    1. void StartConversionThread() {
    2.  
    3. Thread t = new Thread(loadFiles);
    4. t.Start();
    5. }
    Note that loadFiles doesn't have the usual () after it - that's because it's being passed here as a delegate (a reference to the function itself).

    And that will almost work! The main change you'll need to make is to remove the reference to Application.streamingAssetsPath from this function (see point #2 above) - you can just store that in a private string variable on your class anytime before your thread is started.

    Now as for the progress thing, you can once again solve that with a private variable in your class. Your function can access a "isDone" bool, whose status you can check in Update(). Or you could set a "progress" float that does the same.
     
  4. slowDrag0n

    slowDrag0n

    Joined:
    Oct 10, 2017
    Posts:
    41
    thanks for the response. although i was wondering. isnt possible to achieve what i want to do using the async and await. since unity supports this.
     
  5. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,911
    I'm pretty sure you can use async/await with the Task object returned from Task.Run. I've never done it personally.
     
  6. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    As for communicating between the threads, my go to is a ConcurrentQueue.
    https://docs.microsoft.com/en-us/do...concurrent.concurrentqueue-1?view=netcore-3.1
    For example you could do a ConcurrentQueue of strings, and send the path/filename of the files as soon as they finished processing. You could send a specific string you watch for on the main thread to signal that processing is done. You could do additional queues so you can send more detailed progress info, or instead of a queue of strings make it a queue of a more complex custom class which can include path/filename string and any extra progress info you need.
     
    PraetorBlue likes this.