dotnet-standard-sdk: All services should expose Async methods instead of blocking calls that perform async operations internally.
Blocking async operations like HttpRequests or File IO is a bad idea. https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
The internal code that uses IRequest supports Async but it is blocked right before returning to customer code. All APIs should expose Async through the API.
Example:
SpeechModel ISpeechToTextService.GetModel(string modelId, Dictionary<string, object> customData = null);
Should be:
Task<SpeechModel> ISpeechToTextService.GetModelAsync(string modelId, Dictionary<string, object> customData = null, CancellationToken cancellation = default(CancellationToken));
Suggested Solution
Ideally all blocking calls would be marked as Obsolete. Since we need to support backwards compatibility they could be left as warnings only for now.
- Add new async methods for all async operations on all services (ISpeechToTextService, IAssistantService, etc,)
- Mark existing async operations that block internally as Obsolete and direct users to the Async implementation.
Implementation Example
TokenManager.GetTokenAsync example. https://github.com/watson-developer-cloud/dotnet-standard-sdk/blob/development/src/IBM.WatsonDeveloperCloud/Util/TokenManager.cs
public class TokenManager
{
[Obsolete("This method is obsolete. Use GetTokenAsync instead.")]
public string GetToken() => GetTokenAsync().Result;
public async Task<string> GetTokenAsync(CancellationToken cancellation = default(CancellationToken))
{
if (!string.IsNullOrEmpty(_userAccessToken))
{
// 1. use user-managed token
return _userAccessToken;
}
else if (!string.IsNullOrEmpty(_tokenInfo.AccessToken) || IsRefreshTokenExpired())
{
// 2. request an initial token
var tokenInfo = await RequestTokenAsync(cancellation);
SaveTokenInfo(tokenInfo);
return _tokenInfo.AccessToken;
}
else if (this.IsTokenExpired())
{
// 3. refresh a token
var tokenInfo = await RefreshTokenAsync(cancellation);
SaveTokenInfo(tokenInfo);
return _tokenInfo.AccessToken;
}
else
{
// 4. use valid managed token
return _tokenInfo.AccessToken;
}
}
private async Task<IamTokenData> RequestTokenAsync(CancellationToken cancellation)
{
IamTokenData result = null;
try
{
if (string.IsNullOrEmpty(_iamApikey))
throw new ArgumentNullException(nameof(_iamApikey));
var request = this.Client.PostAsync(_iamUrl);
request.WithHeader("Content-type", "application/x-www-form-urlencoded");
request.WithHeader("Authorization", "Basic Yng6Yng=");
List<KeyValuePair<string, string>> content = new List<KeyValuePair<string, string>>();
KeyValuePair<string, string> grantType = new KeyValuePair<string, string>("grant_type", "urn:ibm:params:oauth:grant-type:apikey");
KeyValuePair<string, string> responseType = new KeyValuePair<string, string>("response_type", "cloud_iam");
KeyValuePair<string, string> apikey = new KeyValuePair<string, string>("apikey", _iamApikey);
content.Add(grantType);
content.Add(responseType);
content.Add(apikey);
var formData = new FormUrlEncodedContent(content);
request.WithBodyContent(formData);
result = await request.AsAsync<IamTokenData>(cancellation);
if (result == null)
result = new IamTokenData();
}
catch(AggregateException ae)
{
throw ae.Flatten();
}
return result;
}
//...
}
Workaround
None, the only workaround is to rewrite the services removing the blocking sections.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 2
- Comments: 22 (10 by maintainers)
Here you go. I just added the Extensions so it would build locally.
@atilatosta can you comment on this as well?