Piotr
Piotr

Reputation: 11

Looking for a way to add a progress bar for asynchronic task (Unity, C#)

I'm pretty new to writing asynchronous stuff in Unity. It is pretty easy to create a progress bar for asynchronous operation, but when it comes to doing it for the asynchronous task I'm not even sure when to start :(

public async void GetPhotoFromAndroidAndAddTexture(RawImage mainImage, string path)
{
    await AddTextureToImage(mainImage, path);
}


public async Task<Texture2D> GetTextureFromAndroid(string path)
{
    if(path != "" && path != " ")
    {
        #if UNITY_ANDROID
            path = "file://" + path;
        #endif

        UnityWebRequest www = UnityWebRequestTexture.GetTexture(path);

        var asyncOperation = www.SendWebRequest();

        while(!asyncOperation.isDone)
        {
            await Task.Delay( 1000/30 );
        }

        return DownloadHandlerTexture.GetContent(www);
    }
    else
    {
        Debug.Log("The path is not correct!");
        return null;
    }
}

public async Task AddTextureToImage(RawImage mainImage, string path)
{
    IProgress<int> progress;
    Texture2D photoFromDevice = await GetTextureFromAndroid(path);

    float textureH = photoFromDevice.height;
    float textureW = photoFromDevice.width;

    float aspectRat = textureH > textureW ? textureH/textureW : textureW/textureH;

    mainImage.GetComponent<AspectRatioFitter>().aspectRatio = aspectRat;
    mainImage.texture = photoFromDevice;
}

While the photo from android is being add as a texture I want to create a progress bar. Thank you in advance for any suggestions

Upvotes: 1

Views: 2287

Answers (2)

NullOne
NullOne

Reputation: 25

Today I would suggest you to use UniTask library. It allows to use Coroutines as async methods and vice versa

Then you could write something like this:

// Also, you can use Progress.CreateOnlyValueChanged<float>(...)
var progress = Progress.Create<float>(x => {
     // Do something with progress...
     Debug.Log(x);
});

var request = await UnityWebRequestTexture.GetTexture(path)
    .SendWebRequest()
    .ToUniTask(progress: progress);
return DownloadHandlerTexture.GetContent(request);

Upvotes: 0

derHugo
derHugo

Reputation: 90872

I can see no reason in your code why you should be using async at all. The only thing you actually are waiting for is the UnityWebRequest. async hasn't really any advantage over a simple Coroutine(IEnumerator) here.

In addition async stuff always also brings the problem of multi-threading ... Unity isn't thread safe so most of the API can only be used in the Unity mainthread.


You should rather stick to a Coroutine like e.g.

// wherever you want to show/use the progress
public float progress;

public void GetPhotoFromAndroidAndAddTexture(RawImage mainImage, string path)
{
    StartCoroutine(AddTextureToImage(mainImage, path));
}

public IEnumerator AddTextureToImage(RawImage mainImage, string path)
{
    Texture2D photoFromDevice;

    if (path != "" && path != " ")
    {
#if UNITY_ANDROID
        path = "file://" + path;
#endif

        UnityWebRequest www = UnityWebRequestTexture.GetTexture(path);

        var asyncOperation = www.SendWebRequest();

        while (!asyncOperation.isDone)
        {
            // wherever you want to show the progress:
            progress = www.downloadProgress;

            yield return null;
            // or if you want to stick doing it in certain intervals
            //yield return new WaitForSeconds(0.5f);
        }

        // you should also check for errors
        if(www.error != null) 
        {
            Debug.LogError("Something went wrong loading!", this);
            yield break;
        }

        photoFromDevice = DownloadHandlerTexture.GetContent(www);
    }
    else
    {
        Debug.LogError("The path is not correct!", this);
        yield break;
    }

    float textureH = photoFromDevice.height;
    float textureW = photoFromDevice.width;

    float aspectRat = textureH > textureW ? textureH / textureW : textureW / textureH;

    mainImage.GetComponent<AspectRatioFitter>().aspectRatio = aspectRat;
    mainImage.texture = photoFromDevice;
}

Alternatively often recommended is a kind of "dispatcher" or I prefer to call it a main thread worker where you can pass actions from other threads back to the main thread like e.g.

private ConcurrentQueue<Action> callbacks = new ConcurrentQueue<Action>();

private void Update()
{
    while(callbacks.TryDequeue(var out a))
    {
        // Invokes the action in the main thread
        a?.Invoke();
    }
}

// example method to call
private void UpdateProgressBar(float progress)
{
    // use progress
}

public async Task<Texture2D> GetTextureFromAndroid(string path)
{
    if(path != "" && path != " ")
    {
        #if UNITY_ANDROID
            path = "file://" + path;
        #endif

        UnityWebRequest www = UnityWebRequestTexture.GetTexture(path);

        var asyncOperation = www.SendWebRequest();

        while(!asyncOperation.isDone)
        {
            // add a callback for the main thread using a lambda expression
            callbacks.Add(()=> { UpdateProgressBar(www.downloadProgress); });
            await Task.Delay( 1000/30 );
        }

        return DownloadHandlerTexture.GetContent(www);
    }
    else
    {
        Debug.Log("The path is not correct!");
        return null;
    }
}

A cool way to have a simple progress bar for example can be achieved by using an UI.Image with

  • Image Type = Filled
  • Fill Method = Horizontal
  • Fill Origin = Left

now all you have to do is update the image.fillAmount

Upvotes: 2

Related Questions