Thursday, June 27, 2013

APM wrapper for synchronous methods using Task Library and Roslyn

 

Let me begin by saying this is a very very bad idea. For reasons, read this.

I use two techniques-

  1. Call Func<T> using TaskCompletionSource<TResult> as shown here
  2. Using Tasks to implement APM pattern as shown here

Let’s say you have method with the following signature

int MyLegacyMethod(ref myType param1, string param2, int param3, out param4);


Creating an APM wrapper for a method manually is a x step process-



  1. A one time step – Add an extension method ToApm as shown in the second technique above to implement APM pattern using Tasks.
  2. Write a method MyLegacyMethodAsync as below
    public Task<Tuple<int/*return type*/, myType /*ref param*/, char[] /*out param*/>> MyLegacyMethodAsync(myType param1, string param2, int param3)
    {
    myType param11 = param1; /* ref param for closure */
    char[] param4; /* out param for closure */
    var tcs = new TaskCompletionSource<Tuple<int, myType, char[]>>();

    ThreadPool.QueueUserWorkItem(_ =>
    {
    try
    {

    int retVal = MyLegacyMethod(ref param11 /* local var instead of ref param */, param2, param3, out param4);
    var result = new Tuple<int, myType, char[]>>(retVal, param11, param4);
    tcs.SetResult(result);
    }
    catch (Exception exc)
    {
    tcs.SetException(exc);
    }
    });
    return tcs.Task;
    }

  3. Write a BeginMyLegacyMethod as below
            public IAsyncResult BeginMyLegacyMethod(myType param1, string param2, int param3, AsyncCallback callback, object state)
    {
    return MyLegacyMethodAsync(param1, param2, param3).AsApm(callback, state);
    }


  4. Write a EndMyLegacyMethod as below
            public int EndMyLegacyMethod (IAsyncResult asyncResult, out myType param1, out char[] param4)
    {
    var result = ((Task<Tuple<int, myType, char[]>>) asyncResult).Result;

    param1 = result.Item2;
    param4 = result.Item3;

    return result.Item1;
    }

Now we are able to use our existing APM infrastructure to make APM calls to synchronous methods.


In my scenario, I am converting classes exposed over webservices to direct project reference. There are many classes with many methods and doing this manually would be error prone and mind numbing. So I used Roslyn library and wrote a simple class that



  1. reads in a file

  2. select methods marked with [WebMethod] attributes

  3. Generates the above 3 methods for each such method

  4. Writes them to a file

I have posted this on GitHub gists so that it may help someone save some time.


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
using Roslyn.Services.CSharp;
using Roslyn.Services.Formatting;
namespace GenerateAsyncMethods
{
class Program
{
static List<List<T>> SplitArray<T>(List<T> list, int bucketSize)
{
var buckets = (list.Count() / bucketSize) + 1;
List<List<T>> result = new List<List<T>>();
for (int bucketsTaken = 0; bucketsTaken < buckets; bucketsTaken++)
{
var tempList = list.Skip(bucketsTaken * bucketSize).Take(bucketSize).ToList();
if (tempList.Count() > 0)
result.Add(tempList);
}
return result;
}
static string RecurseType(List<List<string>> typesList)
{
if (typesList.Count() == 0) return string.Empty;
var myTypeList = typesList.Take(1).First();
var subList = RecurseType(typesList.Skip(1).ToList());
var myType = "Tuple<" + String.Join(",", myTypeList) + (string.IsNullOrEmpty(subList) ? string.Empty : "," + subList) + ">";
return myType;
}
static string GetReturnType(MethodDeclarationSyntax m)
{
StringBuilder sb = new StringBuilder();
var rets = new List<string>() { m.ReturnType.Kind != SyntaxKind.VoidKeyword ? m.ReturnType.ToString() : null };
var paramTypeQuery =
from p in m.ParameterList.Parameters
where p.Modifiers.Any(SyntaxKind.RefKeyword) || p.Modifiers.Any(SyntaxKind.OutKeyword)
select p.Type.ToString();
var paramTypes = paramTypeQuery.ToList();
if (m.ReturnType.Kind != SyntaxKind.VoidKeyword)
paramTypes.Insert(0, m.ReturnType.ToString());
var splitTypes = SplitArray(paramTypes, 7);
var typesStr = RecurseType(splitTypes);
sb.Append(typesStr);
System.Console.WriteLine(sb.ToString());
return sb.ToString();
}
static void Main(string[] args)
{
SyntaxTree referenceTree = SyntaxTree.ParseFile(@"..\..\input.cs");
var referenceRoot = (CompilationUnitSyntax)referenceTree.GetRoot();
var referenceMethods = from methodDeclaration in referenceRoot.DescendantNodes().OfType<MethodDeclarationSyntax>()
where methodDeclaration.AttributeLists.Any(alist => alist.Attributes.Any(a => a.Name.ToString() == "WebMethod"))
select methodDeclaration;
var c = Syntax.CompilationUnit();
foreach (MethodDeclarationSyntax m in referenceMethods)
{
c = c
.AddMembers(AddAsyncMethod(m))
.AddMembers(AddBeginForApmMethod(m))
.AddMembers(AddEndForApmMethod(m));
//break;
}
c = c.NormalizeWhitespace();
c = c.WithEndOfFileToken
(
Syntax.Token
(
SyntaxKind.EndOfFileToken
)
);
using (var f = File.Open(@"..\..\output.cs", FileMode.Create, FileAccess.Write))
{
using (var tw = new StreamWriter(f))
{
c.Format(FormattingOptions.GetDefaultOptions());
c.WriteTo(tw);
}
}
}
private static MemberDeclarationSyntax AddEndForApmMethod(MethodDeclarationSyntax m)
{
Expression<Func<string, string>> s = (string str) => !string.IsNullOrEmpty(str) ? "," + str : str;
var prepender = s.Compile();
var restIndexer = "result";
int itemIndexer = m.ReturnType.Kind != SyntaxKind.VoidKeyword ? 2 : 1;
return
Syntax
.MethodDeclaration(
m.ReturnType,
Syntax.ParseToken(string.Format("End{0}", m.Identifier.ToString())))
.WithModifiers(m.Modifiers)
.WithParameterList(
Syntax.ParseParameterList(
string.Format("(IAsyncResult asyncResult{0})",
prepender(
String.Join(",",
(from p in m.ParameterList.Parameters
where p.Modifiers.Any(SyntaxKind.OutKeyword) || p.Modifiers.Any(SyntaxKind.RefKeyword)
select "out " + p.Type.ToString() + " " + p.Identifier.ToString()).ToArray())
)
)
)
)
.WithBody(
Syntax
.Block()
.WithOpenBraceToken(Syntax.Token(SyntaxKind.OpenBraceToken))
.WithStatements(
new SyntaxList<StatementSyntax>()
.Add(
Syntax.ParseStatement(
(m.ParameterList.Parameters.Any(SyntaxKind.RefKeyword) ||
m.ParameterList.Parameters.Any(SyntaxKind.OutKeyword) ||
m.ReturnType.Kind != SyntaxKind.VoidKeyword)
? string.Format("var result = ((Task<{0}>) asyncResult).Result;",
GetReturnType(m)
)
: string.Empty
)
)
.Add(
(from p in m.ParameterList.Parameters
where p.Modifiers.Any(SyntaxKind.OutKeyword) || p.Modifiers.Any(SyntaxKind.RefKeyword)
select Syntax.ParseStatement(
string.Format("{0}={1};"
, p.Identifier.ToString()
,
(
itemIndexer % 7 == 1
? (restIndexer += ".Rest")
: restIndexer
)
+ ".Item" +
(
itemIndexer % 7 == 1
? (
itemIndexer > 1
? (itemIndexer = 2) - 1
: itemIndexer++
)
: itemIndexer++
)
)
)).ToArray()
)
.Add(
Syntax.ParseStatement(
m.ReturnType.Kind != SyntaxKind.VoidKeyword
? "return result.Item1;"
: string.Empty
)
)
)
.WithCloseBraceToken(Syntax.Token(SyntaxKind.CloseBraceToken))
);
}
private static MemberDeclarationSyntax AddBeginForApmMethod(MethodDeclarationSyntax m)
{
Expression<Func<string, string>> s = (string str) => !string.IsNullOrEmpty(str) ? str + "," : str;
var appender = s.Compile();
return
Syntax
.MethodDeclaration(
Syntax.ParseTypeName("IAsyncResult"),
Syntax.ParseToken(string.Format("Begin{0}", m.Identifier.ToString())))
.WithModifiers(m.Modifiers)
.WithParameterList(
Syntax.ParseParameterList(
string.Format("({0}AsyncCallback callback, object state)",
appender(
String.Join(",",
(from p in m.ParameterList.Parameters
where !p.Modifiers.Any(SyntaxKind.OutKeyword)
select p.Type.ToString() + " " + p.Identifier.ToString()).ToArray())
)
)
)
)
.WithBody(
Syntax
.Block()
.WithOpenBraceToken(Syntax.Token(SyntaxKind.OpenBraceToken))
.WithStatements(
Syntax.ParseStatement(
string.Format("return {0}Async({1}).AsApm(callback, state);",
m.Identifier.ToString(),
String.Join(",",
(from p in m.ParameterList.Parameters
where !p.Modifiers.Any(SyntaxKind.OutKeyword)
select p.Identifier.ToString()).ToArray())
)
)
)
.WithCloseBraceToken(Syntax.Token(SyntaxKind.CloseBraceToken))
);
}
private static MethodDeclarationSyntax AddAsyncMethod(MethodDeclarationSyntax m)
{
return Syntax.MethodDeclaration(
Syntax.ParseTypeName("Task<" + GetReturnType(m) + ">"),
Syntax.ParseToken(m.Identifier.ToString() + "Async")
)
.WithModifiers(m.Modifiers)
.WithParameterList(
Syntax.ParseParameterList(
"(" +
String.Join(",",
(from p in m.ParameterList.Parameters
where !p.Modifiers.Any(SyntaxKind.OutKeyword)
select p.Type.ToString() + " " + p.Identifier.ToString()).ToArray()
) +
")")
)
.WithBody(
Syntax
.Block()
.WithOpenBraceToken(Syntax.Token(SyntaxKind.OpenBraceToken))
.WithStatements(
new SyntaxList<StatementSyntax>()
.Add(
(from p in m.ParameterList.Parameters
where p.Modifiers.Any(SyntaxKind.RefKeyword) || p.Modifiers.Any(SyntaxKind.OutKeyword)
select
Syntax.ParseStatement(
p.Type.ToString() + " "
+ p.Identifier.ToString() + (p.Modifiers.Any(SyntaxKind.RefKeyword) ? "1 = " + p.Identifier.ToString() : string.Empty) + ";"
)
).ToArray()
)
.Add(Syntax.ParseStatement("var tcs = new TaskCompletionSource<" + GetReturnType(m) + ">();"))
.Add(Syntax.ParseStatement(
@"ThreadPool.QueueUserWorkItem(_ =>
{
try
{" +
string.Format(
"{0}{1}({2});",
m.ReturnType.Kind != SyntaxKind.VoidKeyword ? "var retVal=" : string.Empty,
m.Identifier.ToString(),
String.Join(",",
(from p in m.ParameterList.Parameters
select string.Format(
"{0} {1}{2}",
p.Modifiers.Any(SyntaxKind.RefKeyword)
? "ref"
: p.Modifiers.Any(SyntaxKind.OutKeyword)
? "out"
: string.Empty,
p.Identifier.ToString(),
p.Modifiers.Any(SyntaxKind.RefKeyword) ? "1" : string.Empty)
).ToArray())) +
string.Format(
"var result={0};",
GetReturnArguments(m)
) +
@"
tcs.SetResult(result);
}
catch (Exception exc)
{
tcs.SetException(exc);
}
});").NormalizeWhitespace())
.Add(Syntax.ParseStatement("return tcs.Task;"))
)
.WithCloseBraceToken(Syntax.Token(SyntaxKind.CloseBraceToken))
);
}
private static string GetReturnArguments(MethodDeclarationSyntax m)
{
StringBuilder sb = new StringBuilder();
var rets = new List<parms>() { m.ReturnType.Kind != SyntaxKind.VoidKeyword ? new parms { type = m.ReturnType.ToString(), identifier = "retVal" } : null };
var paramTypeQuery =
from p in m.ParameterList.Parameters
where p.Modifiers.Any(SyntaxKind.RefKeyword) || p.Modifiers.Any(SyntaxKind.OutKeyword)
select new parms { identifier = p.Identifier.ToString() + (p.Modifiers.Any(SyntaxKind.RefKeyword) ? "1" : string.Empty), type = p.Type.ToString() };
var paramTypes = paramTypeQuery.ToList();
if (rets.Count != 0)
paramTypes.Insert(0, rets[0]);
List<List<parms>> splitTypes = SplitArray(paramTypes, 7);
var typesStr = RecurseParms(splitTypes);
sb.Append(typesStr);
System.Console.WriteLine(sb.ToString());
return sb.ToString();
}
private static string RecurseParms(List<List<parms>> parmsList)
{
if (parmsList.Count() == 0) return null;
var myTempTypeList = parmsList.Select(p1 => p1.Select(p2 => p2.type).ToList()).ToList();
var myTypeList = RecurseType(myTempTypeList);
var myArgsList = (from p in parmsList.Take(1).First()
select p.identifier.ToString()).ToArray();
var subArgsList = RecurseParms(parmsList.Skip(1).ToList());
var myType =
string.Format("new {0}({1})", myTypeList,
String.Join(",", myArgsList) + (string.IsNullOrEmpty(subArgsList) ? string.Empty : "," + subArgsList));
return myType;
}
}
public class parms
{
public string type;
public string identifier;
}
}
view raw Program.cs hosted with ❤ by GitHub

Happy Coding!