1

I have a list wich is loaded with elements each time the user make a research...These elements contain an Icon which is dowloaded with an async method GetByteArrayAsync of the HttpClient object.
I have an issue when the user make a second research while the icon of the first list are still downloading.Because the list of elements is changing while Icon downloads are processing on each element of the first list.
So my guess is that I need to cancel these requests each time the user proceed to a new research...Ive readen some stuuf on Task.run and CancellationTokenSource but I can't find really helpful example for my case so here is my code...
Hope you can help me with that ...Thank you

public static async Task<byte[]> DownloadElementFile(BdeskElement bdeskElement)
{
    //create and send the request

    DataRequest requesteur = new DataRequest();

    byte[] encryptedByte = await requesteur.GetBytesAsync(dataRequestParam);

    return encryptedByte;
}


public async Task<Byte[]> GetBytesAsync(DataRequestParam datarequesparam)
{
    var handler = new HttpClientHandler { Credentials = new NetworkCredential(datarequesparam.AuthentificationLogin, datarequesparam.AuthentificationPassword, "bt0d0000") };
    HttpClient httpClient = new HttpClient(handler);

    try
    {
        byte[] BytesReceived = await httpClient.GetByteArrayAsync(datarequesparam.TargetUri);

        if (BytesReceived.Length > 0)
        {
            return BytesReceived;
        }

        else
        {
            return null;

        }
    }
    catch (WebException)
    {
        throw new MyException(MyExceptionsMessages.Webexception);
    }

}

EDIT

public async Task<Byte[]> GetBytesAsync(DataRequestParam datarequesparam)
        {
            var handler = new HttpClientHandler { Credentials = new NetworkCredential(datarequesparam.AuthentificationLogin, datarequesparam.AuthentificationPassword, "bt0d0000") };
            HttpClient httpClient = new HttpClient(handler);

            try
            {
                cts = new CancellationTokenSource();

                HttpResponseMessage reponse = await httpClient.GetAsync(datarequesparam.TargetUri,cts.Token);

               if (reponse.StatusCode == HttpStatusCode.OK)
               {
                   byte[] BytesReceived = reponse.Content.ReadAsByteArrayAsync().Result;

                   if (BytesReceived.Length > 0)
                   {
                       return BytesReceived;
                   }

                   else
                   {
                       return null;

                   }
               }

               else
               {
                   return null;
               }

            }
            catch (WebException)
            {
                throw new MyException(MyExceptionsMessages.Webexception);
            }
              catch(OperationCanceledException)
            {
                  throw new OperationCanceledException();
            }

EDIT2 I need to cancel this funntion when the user make a new research and the list "listBoxGetDocsLibs" changed.

 private async void LoadIconDocLibs()
        {

            foreach (var doclib in listBoxGetDocsLibs)//ERROR HERE COLLECTION HAS CHANGED
            {

                doclib.Icon = new BitmapImage();


                    try
                    {

                        byte[] Icon = await ServerFunctions.GetDocLibsIcon(doclib);

                        if (Icon != null)
                        {
                            {

                                var ms = new MemoryStream(Icon);

                                BitmapImage photo = new BitmapImage();
                                photo.DecodePixelHeight = 64;
                                photo.DecodePixelWidth = 92;
                                photo.SetSource(ms);
                                doclib.Icon = photo;
                            }
                        }
                    }
                    catch(OperationCanceledException)
                    {

                    }
                }


        }
Paul Martinez
  • 562
  • 1
  • 9
  • 19

1 Answers1

2

First you need to define CancellationTokenSource:

private System.Threading.CancellationTokenSource cts;

place above code somewhere, where you can access it with your Button or other method.

Unfortunately GetByteArrayAsync lacks Cancelling - so it cannot be used with cts.Token, but maybe you can accomplish your task using GetAsync - which supports Cancelling:

ctsDownload = new System.Threading.CancellationTokenSource();
HttpResponseMessage response = await httpClient.GetAsync(requestUri, cts.Token);

Then you can get your content from response.

And when you want to Cancel your Task it can look like this:

private void cancelBtn_Click(object sender, RoutedEventArgs e)
{
   if (this.cts != null)
       this.cts.Cancel();
}

When you Cancel task an Exception will be thrown.

If you want to cancel your own async Task, a good example you can find at Stephen Cleary blog.

EDIT - you can also build your own method (for example with HttpWebRequest) which will support Cancelling:

For this purpose you will have to extend HttpWebRequest (under WP it lacks GetResponseAsync):

// create a static class in your namespace
public static class Extensions
{
    public static Task<HttpWebResponse> GetResponseAsync(this HttpWebRequest webRequest)
    {
        TaskCompletionSource<HttpWebResponse> taskComplete = new TaskCompletionSource<HttpWebResponse>();
        webRequest.BeginGetResponse(
            asyncResponse =>
            {
                try
                {
                    HttpWebRequest responseRequest = (HttpWebRequest)asyncResponse.AsyncState;
                    HttpWebResponse someResponse = (HttpWebResponse)responseRequest.EndGetResponse(asyncResponse);
                    taskComplete.TrySetResult(someResponse);
                }
                catch (WebException webExc)
                {
                    HttpWebResponse failedResponse = (HttpWebResponse)webExc.Response;
                    taskComplete.TrySetResult(failedResponse);
                }
                catch (Exception exc) { taskComplete.SetException(exc); }
            }, webRequest);
        return taskComplete.Task;
    }
}

Then your method can look like this:

public async Task<Byte[]> GetBytesAsync(DataRequestParam datarequesparam, CancellationToken ct)
{
   HttpWebRequest request = HttpWebRequest.CreateHttp(datarequesparam.TargetUri);
   request.Method = "GET";
   request.Credentials = new NetworkCredential(datarequesparam.AuthentificationLogin, datarequesparam.AuthentificationPassword, "bt0d0000");
   request.AllowReadStreamBuffering = false;

   try
   {
       if (request != null)
       {
           using (HttpWebResponse response = await request.GetResponseAsync())
           using (Stream mystr = response.GetResponseStream())
           using (MemoryStream output = new MemoryStream())
           {
               const int BUFFER_SIZE = 10 * 1024;
               byte[] buf = new byte[BUFFER_SIZE];

               int bytesread = 0;
               while ((bytesread = await mystr.ReadAsync(buf, 0, BUFFER_SIZE)) > 0)
               {
                   output.Write(buf, 0, bytesread);
                   ct.ThrowIfCancellationRequested();
               }
               return output.ToArray();
           }
       }
       else return null;
  }
  catch (WebException)
  {
      throw new MyException(MyExceptionsMessages.Webexception);
  }
}

You can freely change Buffer Size which will affect how often Cancellation will be checked.

I haven't tried this but I think it should work.

Romasz
  • 29,662
  • 13
  • 79
  • 154
  • I already tried o add the token parameter but GetByteArrayAsync don't accept this kind of parameters...(only string or Uri) – Paul Martinez Mar 07 '14 at 10:06
  • @PaulMartinez You are right this method doesn't support Cancelling. It has to be done then with different method or implementation. – Romasz Mar 07 '14 at 10:20
  • @PaulMartinez But maybe you can use other methods which support Cancelling - I've edited my answer. – Romasz Mar 07 '14 at 10:31
  • @PaulMartinez In case you need to return a byte array, you can build your own method that will support cancelling - I've added sample code in my next edit. I've not tried it so it may need some improvements. – Romasz Mar 07 '14 at 10:53
  • Thanks the edited code i pasted is working great.I can get a byte[] and the cancel operation works too...But my problem it's not on this part actually , my problem is located more on the UI code...In fact I need to stop my own Function by using a task.run...but it will be tricky because this function can only work on the pool thread...(BitmapImage are instancied)...so I probably have to call the dispatcher inside a task excetuting on a new thread... – Paul Martinez Mar 07 '14 at 11:29
  • @PaulMartinez If you want to access UI elements from Task/Thread then you will have to use Dispatcher. I think it would be more suitable if you had asked new question concerning this problem, cause what you have said is hard for me to understand without further explanation and probably code. Thanks for the information that code worked. – Romasz Mar 07 '14 at 11:41
  • thank you...but I found how to solve my issue in a totally different way I don't need to cancel my async call when my list is reloaded...I only change the line of code following this recommandation [LINK](http://stackoverflow.com/questions/604831/collection-was-modified-enumeration-operation-may-not-execute) – Paul Martinez Mar 07 '14 at 13:08
  • @PaulMartinez You asked about a way how to Cancel Async Task and I've talked only about that (I'm not aware of other problems in your code). Nevertheless I'm glad you have solved your problems :) – Romasz Mar 07 '14 at 13:14