Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
12 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by z3nth10n · Oct 29, 2018 at 09:40 PM · coroutinesprogress-barstream

Read file while updating progress bar with coroutines

I'm trying to read a file line by line while I update a progress bar (two GUI textures with a float expansion in one of its width (maxWidth * currentPercentage)).

I have two implementations:

     public static string ThreadedFileRead(string path, Action<float> percAction)
     {
         FileInfo fileInfo = new FileInfo(path);
         StringBuilder sb = new StringBuilder();

         float length = fileInfo.Length;
         int currentLength = 0;

         using (StreamReader sr = new StreamReader(path))
         {
             while (!sr.EndOfStream)
             {
                 string str = sr.ReadLine();
                 sb.AppendLine(str);

                 // yield return str;

                 percAction(currentLength / length);
                 currentLength += str.Length;
                 Interlocked.Add(ref currentLength, str.Length);
             }

             percAction(1f);

             return sb.ToString();
         }
     }

Using the following implementation:

   // Inside a MonoBehaviour

   public void Start() 
   {
        string fileContents = "";
        StartCoroutine(LoadFileAsync(Application.dataPath + "/Data/file.txt", (s) => fileContents = s));
   }

   public IEnumerator LoadFileAsync(string path, Action<string> fin) 
   {
         string contents = "";

         lock (contents)
         {
             var task = Task.Factory.StartNew(() =>
             {
                 contents = F.ThreadedFileRead(path, (f) => currentLoadProgress = f);
             });

             while (!task.IsCompleted)
                 yield return new WaitForEndOfFrame();

             fin?.Invoke(contents);
         }
   }

But this blocks the current GUI (I don't know why).

I also used this:

     // Thanks to: https://stackoverflow.com/questions/41296957/wait-while-file-load-in-unity
     // Thanks to: https://stackoverflow.com/a/34378847/3286975
     [MustBeReviewed]
     public static IEnumerator LoadFileAsync(string pathOrUrl, Action<float> updatePerc, Action<string> finishedReading)
     {
         FileInfo fileInfo = new FileInfo(pathOrUrl);

         float length = fileInfo.Length;

         // Application.isEditor && ??? // Must review
         if (Path.IsPathRooted(pathOrUrl))
             pathOrUrl = "file:///" + pathOrUrl;

         /*

         using (var www = new UnityWebRequest(pathOrUrl))
         {
             www.downloadHandler = new DownloadHandlerBuffer();

             CityBenchmarkData.StartBenchmark(CityBenchmark.SendWebRequest);

             yield return www.SendWebRequest();

             CityBenchmarkData.StopBenchmark(CityBenchmark.SendWebRequest);

             while (!www.isDone)
             {
                 // www.downloadProgress
                 updatePerc?.Invoke(www.downloadedBytes / length); // currentLength / length
                 yield return new WaitForEndOfFrame();
             }

             finishedReading?.Invoke(www.downloadHandler.text);
         }

          */

         using (var www = new WWW(pathOrUrl))
         {
             while (!www.isDone)
             {
                 // www.downloadProgress
                 updatePerc?.Invoke(www.bytesDownloaded / length); // currentLength / length
                 yield return new WaitForEndOfFrame();
             }

             finishedReading?.Invoke(www.text);
         }
     }

With the following implementation:

   public IEnumerator LoadFileAsync(string path, Action<string> fin) 
   {
        yield return F.LoadFileAsync(path, (f) => currentLoadProgress = f, fin);
   }

The last code I shared has two parts:

  • The commented part blocks also the main thread.

  • The WWW class I used (it will be deprecated in a future) doesn't block the main thread, but it only displays two steps on the progress bar (like 25% and 70%).

I don't why this is happening and if there is a better approach for this.

So, any help (guidance) for this is welcome.

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

1 Reply

· Add your reply
  • Sort: 
avatar image
2

Answer by Bunny83 · Oct 30, 2018 at 02:16 AM

Wow, the amount of bad habits and errors in those code examples is kinda scary ^^.

Lets start with the "ThreadedFileRead" method. The first thing that could cause issues are those two lines:

 string str = sr.ReadLine();
 sb.AppendLine(str);

I'll assume that you actually read text files, otherwise using ReadLine wouldn't make any sense. The problem here is that ReadLine will remove the line seperation character(s) and AppendLine will add line seperation character(s). Depending on the platform that could be one or two characters. If you're on windows it generally uses two (0x0D + 0x0A == "\r\n"). If the original text only contains line feed characters the resulting text will be longer. On the other hand on unix / mac / linux / android we usually have only one character (most the time just a line feed "\n", though mac also uses just a carriage return "\r").


This leads to the next issue that incrementing the total byte count by the string length will actually miss the line seperation characters which are of course included in the file size. So you will never reach the actual filesize.


The next strange thing are those two lines:

 currentLength += str.Length;
 Interlocked.Add(ref currentLength, str.Length);

Both will increment currentLength by the str.Length while only one should be used. Using Interlocked add on a local variable that is only used locally makes no sense.


About the second code block, i guess the Start method is not the actual code since using a local string variable in a lambda makes little sense since the variable is out of scope when the thread finishes executing. Unless there's another closure that closes over the same local variable for later use this just looks wrong.


Next thing is the lock inside your coroutine. First of all it's just pointless. A lock only makes sense if another thread may lock on the same object and only one thread can execute at that time. The next related issue is that you should generally not use strings as lock objects for several reasons. In this case you actually lock on the empty string object. This is the same across all empty strings since it's an interned value. Keep in mind that locks work on the object value, not on variables. Finally a lock that spans over the whole procedure simply enforces synchonous execution if another thread also uses the same lock object. You should aquire a lock only when necessary and hold it as short as possible. Using locks for a long time makes multithreading pretty pointless.


The next issue may be "Task.Factory.StartNew". This topic is a bit too complex to handle here, but you may want to read this blog carefully


Next, don't use "WaitForEndOfFrame" unless you really need it (and that's almost never the case). The only cases where you actually want to use it is when you want to do additional rendering at the end of a frame after everything is done. Creating a "WaitForEndOfFrame" object requires memory. If you just want to run each frame, return "null".


About your third code block. The commented out code will not produce any increasing percentage as you yield on "www.SendWebRequest();". this will pause the coroutine until the download is completed


The threaded approach should work, though i wouldn't recommend using "Task.Factory.StartNew" but simply "Task.Run". Note that nowadays even quite large files are loaded within a fraction of a second. Reading a large file line by line will actually make the speed worse, a lot. That's due to the large amount of memory you're allocating and all the internal buffer copying that you're stringbuilder has to perform. This can be avoided by setting the capacity at the beginning:

 float length = fileInfo.Length;
 StringBuilder sb = new StringBuilder(length);

Though still reading a file line by line is always slower than reading the whole file at once.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

96 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Checking Input on 2 different coroutines at the same time 1 Answer

My Coroutine does not get called 4 Answers

Wait in a coroutine without busy-waiting? 1 Answer

How to get a constantly updating variable to a non-constantly updated function. 1 Answer

move inside a coroutine 2 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges