Let me begin by saying this is a very very bad idea. For reasons, read this.
I use two techniques-
- Call Func<T> using TaskCompletionSource<TResult> as shown here
- 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-
- A one time step – Add an extension method ToApm as shown in the second technique above to implement APM pattern using Tasks.
- 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;
} - 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);
} - 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
- reads in a file
- select methods marked with [WebMethod] attributes
- Generates the above 3 methods for each such method
- Writes them to a file
I have posted this on GitHub gists so that it may help someone save some time.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
Happy Coding!