1

Would like to try using AsyncCTP with TFS. Currently have a long running method that calls RunQuery on a TFS Query instance.

Query exposes the APM methods BeginQuery() and EndQuery(). As I understand it, the recommended approach to wrap these using AsyncCTP is something like: (example from docs)

Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, buffer, offset, count, null);

Further, have wrapped it in an extension method as in the docs so my actual method looks like:

public static Task<WorkItemCollection> RunQueryAsync(this Query query)
{
    if (query== null) 
        throw new ArgumentNullException("Query");

    return Task<WorkItemCollection>.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
} 

...but this fails to compile. Getting an "invalid argument" intellisense error that, frankly, I can't really understand because the types and format look correct. One possible issue might be that the Query APM methods expect an ICanceleableAsyncResult whereas the Task factory is expecting an IAsyncResult -- but looking at the TFS API, ICanceleableAsyncResult is a specialization of IAsyncResult.

Not sure whether i'm doing it wrong or its just not possible. Would love to be able to do it the AsyncCTP way but may have to go back to the APM pattern -- ugh!

Tim
  • 11
  • 2
  • See my updated answer; I compiled it myself this time, which always helps ;). P.S. Robert Harvey meant no disrespect when he deleted your answer; Stack Overflow is meant to me a Q&A site rather than a forum. You can ask related follow-up questions or make comments (like "it doesn't compile" :) by clicking the "add comment" link under the relevant answer. – Stephen Cleary Sep 28 '11 at 01:06

1 Answers1

4

Update: My Nito.AsyncEx library now includes a TeamFoundationClientAsyncFactory type, which can be used instead of rolling your own implementation below.


The TFS API is not strictly following the APM pattern because it does not take a state parameter, and this is preventing the built-in TaskFactory.FromAsync from working.

You'll have to write your own FromAsync equivalent, which can be done using TaskCompletionSource:

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Client;

public static class TfsUtils<TResult>
{
  public static Task<TResult> FromTfsApm(Func<AsyncCallback, ICancelableAsyncResult> beginMethod, Func<ICancelableAsyncResult, TResult> endMethod, CancellationToken token)
  {
    // Represent the asynchronous operation by a manually-controlled task.
    TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
    try
    {
      // Begin the TFS asynchronous operation.
      var asyncResult = beginMethod(Callback(endMethod, tcs));

      // If our CancellationToken is signalled, cancel the TFS operation.
      token.Register(asyncResult.Cancel, false);
    }
    catch (Exception ex)
    {
      // If there is any error starting the TFS operation, pass it to the task.
      tcs.TrySetException(ex);
    }

    // Return the manually-controlled task.
    return tcs.Task;
  }

  private static AsyncCallback Callback(Func<ICancelableAsyncResult, TResult> endMethod, TaskCompletionSource<TResult> tcs)
  {
    // This delegate will be invoked when the TFS operation completes.
    return asyncResult =>
    {
      var cancelableAsyncResult = (ICancelableAsyncResult)asyncResult;

      // First check if we were canceled, and cancel our task if we were.
      if (cancelableAsyncResult.IsCanceled)
        tcs.TrySetCanceled();
      else
      {
        try
        {
          // Call the TFS End* method to get the result, and place it in the task.
          tcs.TrySetResult(endMethod(cancelableAsyncResult));
        }
        catch (Exception ex)
        {
          // Place the TFS operation error in the task.
          tcs.TrySetException(ex);
        }
      }
    };
  }
}

You can then use it in extension methods as such:

using System.Threading;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

public static class TfsExtensions
{
  public static Task<WorkItemCollection> QueryAsync(this Query query, CancellationToken token = new CancellationToken())
  {
    return TfsUtils<WorkItemCollection>.FromTfsApm(query.BeginQuery, query.EndQuery, token);
  }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Stephen, have you tried how those perform with multiple queries / workitemstore activities going on from the same client concurrently? MS mentions that each thread should open its own WorkItemStore instance in multi-threaded scenarios (http://msdn.microsoft.com/en-us/library/bb130347.aspx). – Jörg Battermann Feb 04 '12 at 14:21
  • I do not have a TFS server available (we use SVN or Hg). My code just provides a `Task` wrapper for the existing `ICancelableAsyncResult` APIs, so the threading restrictions would be identical. The MSDN doc you referenced talks about multithreading but not concurrent operations from a single thread. – Stephen Cleary Feb 04 '12 at 15:36
  • Yeah concurrency != multithreading but I meant if the underlying TaskScheduler would indeed use separate thread(s).. then it 'could' be a problem. Need to dig a little bit deeper, benchmark and see what happens. – Jörg Battermann Feb 04 '12 at 23:00