patterncsharpMinor
TPL Async Await in Console Apps
Viewed 0 times
asyncawaitappsconsoletpl
Problem
I just picked up Async Await and I am trying to use Async Await in Console App along with TPL for querying webservices. The code works fine and I am able to get the speed using HttpClient and async. Wanted to know if I am overusing async/await and if there is anything that I should avoid and can be simplified further.
I have read Albahari's threading e-book and the following articles to understand the usage.
http://www.tugberkugurlu.com/archive/how-and-where-concurrent-asynchronous-io-with-asp-net-web-api
https://web.archive.org/web/20160718142900/http://blogs.msdn.com:80/b/pfxteam/archive/2011/01/13/10115163.aspx
https://scalablenotions.wordpress.com/2015/05/02/tpl-and-async-await-best-practices-for-the-busy-developer/
```
class Program
{
public class Member
{
public string N { get; set; }
public string E { get; set; }
}
static void Main()
{
List list = new List();
Task.Run(async () => { await SubscribeMembersusingAPI(list); }).GetAwaiter().GetResult();
}
// Define other methods and classes here
public static async Task SubscribeMembersusingAPI(List members)
{
List updateemailstosubscribed = new List();
HttpClient client = new HttpClient();
try
{
await Task.WhenAll(members.Select(m => SubscribeusingAPI(client, m)));
}
catch (Exception e)
{
//logger.Error("Error in Async task", e);
}
return;
}
public static async Task SubscribeusingAPI(HttpClient client, Member member)
{
try
{
List> listkvp = new List>();
listkvp.Add(new KeyValuePair("e", member.E.Trim()));
listkvp.Add(new KeyValuePair("n", member.N.Trim()));
listkvp.Add(new KeyValuePair("boolean", "true"));
FormUrlEncodedContent formpost = new FormUrlEncodedContent(listkvp);
var response = await client.PostAsync("", formpost);
string
I have read Albahari's threading e-book and the following articles to understand the usage.
http://www.tugberkugurlu.com/archive/how-and-where-concurrent-asynchronous-io-with-asp-net-web-api
https://web.archive.org/web/20160718142900/http://blogs.msdn.com:80/b/pfxteam/archive/2011/01/13/10115163.aspx
https://scalablenotions.wordpress.com/2015/05/02/tpl-and-async-await-best-practices-for-the-busy-developer/
```
class Program
{
public class Member
{
public string N { get; set; }
public string E { get; set; }
}
static void Main()
{
List list = new List();
Task.Run(async () => { await SubscribeMembersusingAPI(list); }).GetAwaiter().GetResult();
}
// Define other methods and classes here
public static async Task SubscribeMembersusingAPI(List members)
{
List updateemailstosubscribed = new List();
HttpClient client = new HttpClient();
try
{
await Task.WhenAll(members.Select(m => SubscribeusingAPI(client, m)));
}
catch (Exception e)
{
//logger.Error("Error in Async task", e);
}
return;
}
public static async Task SubscribeusingAPI(HttpClient client, Member member)
{
try
{
List> listkvp = new List>();
listkvp.Add(new KeyValuePair("e", member.E.Trim()));
listkvp.Add(new KeyValuePair("n", member.N.Trim()));
listkvp.Add(new KeyValuePair("boolean", "true"));
FormUrlEncodedContent formpost = new FormUrlEncodedContent(listkvp);
var response = await client.PostAsync("", formpost);
string
Solution
Let’s try to apply SRP, and get rid of mutable state to increase code maintainability. It inflates C# code a little bit, but usually makes sense.
Demo:
Where
We will use this universal helper class to log task exceptions:
Now subscription helpers:
Subscription request:
And response:
The only missing part:
P.S. Generally speaking, C# is a pretty verbose language, so it is often punishing good programming practices…
Demo:
IEnumerable members = new Member[] { new Member("n","e") };
await members
.SubscribeAsync()
.LogAsync(Console.Out);Where
Member could be immutable:public class Member
{
public Member(string n, string e)
{
N = n.Trim();
E = e.Trim();
}
public string N { get; }
public string E { get; }
public override string ToString() => $"N={N}, E={E}";
}We will use this universal helper class to log task exceptions:
static class Logging
{
public static async Task LogAsync(this Task task, TextWriter writer)
{
try
{
await task;
}
catch (AggregateException ex)
{
foreach (var exx in ex.Flatten().InnerExceptions)
await writer.WriteLineAsync(exx.Message);
}
catch(Exception ex)
{
await writer.WriteLineAsync(ex.Message);
}
}
}Now subscription helpers:
static class Subscription
{
public static Task SubscribeAsync(this IEnumerable members) =>
Task.WhenAll(from member in members
select member.SubscribeAsync());
public static Task SubscribeAsync(this Member member) =>
new SubscriptionRequest(member).ExecuteAsync();
}Subscription request:
public class SubscriptionRequest
{
const string RequestUrl = "";
public SubscriptionRequest(Member member)
{
Member = member;
}
public async Task ExecuteAsync()
{
using (var client = new HttpClient())
try
{
var response = await client.PostAsync(RequestUrl, Content);
var responseBody = await response.Content.ReadAsStringAsync();
return new SubscriptionResponse(responseBody);
}
catch(Exception ex)
{
throw new SubscriptionException(Member, ex);
}
}
FormUrlEncodedContent Content => new FormUrlEncodedContent(Kvps);
IEnumerable> Kvps => new[]
{
new KeyValuePair("e", Member.E),
new KeyValuePair("n", Member.N),
new KeyValuePair("boolean", "true")
};
Member Member { get; }
}And response:
public class SubscriptionResponse
{
public SubscriptionResponse(string body)
{
Body = body;
if (!Success)
throw new InvalidOperationException(body);
}
public bool Subscribed => Body == "1";
public bool AlreadySubscribed => Body == "Member present";
bool Success => Subscribed || AlreadySubscribed;
string Body { get; }
}The only missing part:
public class SubscriptionException : Exception
{
public SubscriptionException(Member member, Exception error) :
base($"{member} subscription failed, error: {error.Message}", error)
{
}
}P.S. Generally speaking, C# is a pretty verbose language, so it is often punishing good programming practices…
Code Snippets
IEnumerable<Member> members = new Member[] { new Member("n","e") };
await members
.SubscribeAsync()
.LogAsync(Console.Out);public class Member
{
public Member(string n, string e)
{
N = n.Trim();
E = e.Trim();
}
public string N { get; }
public string E { get; }
public override string ToString() => $"N={N}, E={E}";
}static class Logging
{
public static async Task LogAsync(this Task task, TextWriter writer)
{
try
{
await task;
}
catch (AggregateException ex)
{
foreach (var exx in ex.Flatten().InnerExceptions)
await writer.WriteLineAsync(exx.Message);
}
catch(Exception ex)
{
await writer.WriteLineAsync(ex.Message);
}
}
}static class Subscription
{
public static Task SubscribeAsync(this IEnumerable<Member> members) =>
Task.WhenAll(from member in members
select member.SubscribeAsync());
public static Task<SubscriptionResponse> SubscribeAsync(this Member member) =>
new SubscriptionRequest(member).ExecuteAsync();
}public class SubscriptionRequest
{
const string RequestUrl = "";
public SubscriptionRequest(Member member)
{
Member = member;
}
public async Task<SubscriptionResponse> ExecuteAsync()
{
using (var client = new HttpClient())
try
{
var response = await client.PostAsync(RequestUrl, Content);
var responseBody = await response.Content.ReadAsStringAsync();
return new SubscriptionResponse(responseBody);
}
catch(Exception ex)
{
throw new SubscriptionException(Member, ex);
}
}
FormUrlEncodedContent Content => new FormUrlEncodedContent(Kvps);
IEnumerable<KeyValuePair<string, string>> Kvps => new[]
{
new KeyValuePair<string, string>("e", Member.E),
new KeyValuePair<string, string>("n", Member.N),
new KeyValuePair<string, string>("boolean", "true")
};
Member Member { get; }
}Context
StackExchange Code Review Q#131643, answer score: 3
Revisions (0)
No revisions yet.