Forum Xamarin Xamarin.Android

How to avoid blocking UI and cancel an Async Network Request

This is two questions in one.

  1. Why does my Async method block my UI?
  2. How can I cancel the Async method (an FtpWebRequest)?

I've got an array of photo ids that refer to photos stored in my documents directory. I want to send each one of them via FTP to my server (they're about 1MB each).

Here's the code in 3 simple methods. In the first method I show my activity indicator, create a cancellation token and call the other two methods, CancelFtp and SendPhoto.

The first problem is that even though the SendPhoto method is called asynchronously, my UI blocks for 4 to 5 seconds (on my Galaxy S5) before the activity indicator shows on screen. Why?

The second problem is that I can't figure out how to cancel the FTP. I can set the CancellationToken in CancelFtp(), but how can I read the token in the SendPhotos method when the requestStream is running?

    private async Task<bool> Send()
    {
        activityIndicator.Visibility = ViewStates.Visible;
        CancelFtp(photoGuids.Count);//sets the overall timeout - 10 secs * number of photos

        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken ct = cts.Token;

        for (int i = 0; i < photoGuids.Count; i++)
        {
            string photoGuid = photoGuids[i];
            await SendPhoto(photoGuid, ct);
        }

        return true;
    }

    private async Task<bool> CancelFtp(int i)
    {
        await Task.Delay(10000 * i);
        cts.Cancel();

        return true;
    }

    async public Task<bool> SendPhoto(string photoGuid, CancellationToken token)
    {

        string ftpUser = "xxx";
        string ftpPass = "xxxx";
        string ftpUrl = "xxxx";

        try
        {
            string imageId = photoGuid + ".png";
            var documentsDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
            string fullPath = System.IO.Path.Combine(documentsDirectory, imageId);

            Bitmap mainPhoto = BitmapFactory.DecodeFile(fullPath, null);

            byte[] bitmapData;
            using (var stream = new MemoryStream())
            {
                mainPhoto.Compress(Bitmap.CompressFormat.Png, 0, stream);
                bitmapData = stream.ToArray();
            }

            string filename = System.IO.Path.GetFileName(fullPath);
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(ftpUrl + filename);
            request.Method = WebRequestMethods.Ftp.UploadFile;
            request.Credentials = new NetworkCredential(ftpUser, ftpPass);
            Stream requestStream = await request.GetRequestStreamAsync(); //How to cancel this?
            requestStream.Write(bitmapData, 0, bitmapData.Length);
            requestStream.Close();

        }
        catch (System.OperationCanceledException e)
        {
            Console.WriteLine("Cancel ex {0}", e.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

        return true;
    }

If I comment out the call to SendPhoto, the UI updates immediately, so it certainly looks like the blocking is in there. As far as the cancellation is concerned, I've tried setting a timeout on the request, but that only seems to work if its a synchronous FtpWebRequest.

I've also tried adding .ConfigureAwait(false); to the requestStream, but that's not doing anything for me either.

Best Answer

Answers

  • BerayBentesenBerayBentesen TRUniversity ✭✭✭✭

    @PhilipJohn there are some method to prevent UI

    Check this post to make web / http request Thread/UI friendly.

    Also you can start work in a new Thread like this :

        new Thread(() => RunOnUiThread(() => yourMethod())).Start();
    
  • PhilipJohnPhilipJohn USMember ✭✭

    @BerayBentesen Thanks for the link and suggestion. I might end up creating a separate thread as you suggest, but I'd also like to understand why the way I'm using async/await is blocking the UI.

Sign In or Register to comment.