简体   繁体   中英

Trying to run a Task in .net 4.0 (using SSIS)

I've got an SSIS package which contains a few scripts. One of them gets a token via a rest api.

I wrote the below, which works fine in .net 4.5 - however, our server isn't that up to date and we've got to downgrade to .net 4.0, which is where our hiccups begin as 4.0 has no GetAwaiter (and various other handy functions).

To add to the fun, as mentioned this is SSIS, where I believe you cannot add NuGet packages.

    public void Main()
    {
        //read the token from the file
        string ref_token = File.ReadAllText(Dts.Variables["User::PATH_refresh_token"].Value.ToString());
        **Dts.Variables["User::API_returned"].Value = GetToken(Dts.Variables["$Package::API_token_client_id"].Value.ToString(), Dts.Variables["$Package::API_token_client_secret"].Value.ToString(), ref_token, Dts.Variables["$Package::API_token_endpoint"].Value.ToString()).GetAwaiter().GetResult();**
    }

    static async Task<string> GetToken(string client_id, string client_secret, string ref_token, string token_url)
    {
        try
        {
            var client = new System.Net.Http.HttpClient();
            HttpContent content = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("client_id", client_id),
                new KeyValuePair<string, string>("client_secret", client_secret),
                new KeyValuePair<string, string>("grant_type", "refresh_token"),
                new KeyValuePair<string, string>("refresh_token", ref_token),
                new KeyValuePair<string, string>("expiration", "20160")
            });
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
            **var response = await client.PostAsync(token_url, content);**
            var responseString = await response.Content.ReadAsStringAsync();
            return responseString;
        }
        catch (Exception exception)
        {
            return exception.ToString();
        }
    }

So we're currently having issues with the GetAwaiter() (the fifth line in the code, highlighted):

'Task(string)' does not contain a definition for 'GetAwaiter'...

And also with the await in this line (also highlighted):

var response = await client.PostAsync(token_url, content);

'Task(HttpResponseMessage)' does not contain a definition for 'GetAwaiter'...

Unfortunately the party I'm trying to connect to uses TLS12 and TLS11, which aren't supported in .net 4.0.

Well, then fixing the GetAwaiter issue isn't going to help.

But for posterity:

Any code using await can be transformed to equivalent code not using await . To do this, you'd need to split up your code by hand and use ContinueWith instead of await and TaskCompletionSource<T> instead of async . This is roughly what the compiler is doing to your code anyway.

Step by step:

First, you need to replace async with TaskCompletionSource<T> :

static Task<string> GetToken(...)
{
  var tcs = new TaskCompletionSource<string>();
  try
  {
    ...
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
    var response = await client.PostAsync(token_url, content);
    var responseString = await response.Content.ReadAsStringAsync();
    return responseString;
  }
  catch (Exception exception)
  {
    return exception.ToString();
  }
}

All return statements now become code that sets the result of the TCS:

static Task<string> GetToken(...)
{
  var tcs = new TaskCompletionSource<string>();
  try
  {
    ...
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
    var response = await client.PostAsync(token_url, content);
    var responseString = await response.Content.ReadAsStringAsync();
    tcs.TrySetResult(responseString);
  }
  catch (Exception exception)
  {
    tcs.TrySetResult(exception.ToString());
  }
}

Next, remove the try / catch (but remember it's there). With ContinueWith we'll need to handle errors within the continuations:

static Task<string> GetToken(...)
{
  var tcs = new TaskCompletionSource<string>();
  ...
  ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
  var response = await client.PostAsync(token_url, content);
  var responseString = await response.Content.ReadAsStringAsync();
  tcs.TrySetResult(responseString);
//  catch (Exception exception)
//  {
//    tcs.TrySetResult(exception.ToString());
//  }
}

Now you can start converting the await statements to ContinueWith . For each one, move the rest of the method into a continuation. Note that ContinueWith is dangerous , so be sure to pass the correct scheduler. This code does not look like it needs the original context, so I'm using TaskScheduler.Default . So technically, this is a translation of await with ConfigureAwait(false) and not just a plain await , which would be more complex.

The continuation gets a task, which it can query for exceptions or results. Be aware of which members wrap exceptions in AggregateException ; that can change your exception handling code.

This is what the first await transformation looks like:

static Task<string> GetToken(...)
{
  var tcs = new TaskCompletionSource<string>();
  ...
  ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
  client.PostAsync(token_url, content).ContinueWith(task =>
  {
    if (task.IsFaulted)
    {
      tcs.TrySetResult(task.Exception.InnerException.ToString());
      return;
    }

    var response = task.Result;
    var responseString = await response.Content.ReadAsStringAsync();
    tcs.TrySetResult(responseString);
  }, TaskScheduler.Default);
//  catch (Exception exception)
//  {
//    tcs.TrySetResult(exception.ToString());
//  }
}

The second await transformation can be done in a similar way:

static Task<string> GetToken(...)
{
  var tcs = new TaskCompletionSource<string>();
  ...
  ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
  client.PostAsync(token_url, content).ContinueWith(task =>
  {
    if (task.IsFaulted)
    {
      tcs.TrySetResult(task.Exception.InnerException.ToString());
      return;
    }

    var response = task.Result;
    response.Content.ReadAsStringAsync().ContinueWith(task2 =>
    {
      if (task2.IsFaulted)
      {
        tcs.TrySetResult(task2.Exception.InnerException.ToString());
        return;
      }

      var responseString = task2.Result;
      tcs.TrySetResult(responseString);
    }, TaskScheduler.Default);
  }, TaskScheduler.Default);
}

Alternatively, with simple await statements one after another, you can "chain" the continuations. You do need to use Unwrap since the first continuation returns a task. This approach looks like this:

static Task<string> GetToken(...)
{
  var tcs = new TaskCompletionSource<string>();
  ...
  ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
  client.PostAsync(token_url, content).ContinueWith(task =>
  {
    if (task.IsFaulted)
    {
      tcs.TrySetResult(task.Exception.InnerException.ToString());
      return;
    }

    var response = task.Result;
    return response.Content.ReadAsStringAsync();
  }, TaskScheduler.Default)
  .Unwrap()
  .ContinueWith(task =>
  {
    if (task.IsFaulted)
    {
      tcs.TrySetResult(task.Exception.InnerException.ToString());
      return;
    }

    var responseString = task.Result;
    tcs.TrySetResult(responseString);
  }, TaskScheduler.Default);
}

As a final note, with "chained" continuations, many people prefer to flow the exceptions through and flatten them at the end; the code is a little shorter that way:

static Task<string> GetToken(...)
{
  var tcs = new TaskCompletionSource<string>();
  ...
  ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
  client.PostAsync(token_url, content).ContinueWith(task =>
  {
    var response = task.Result;
    return response.Content.ReadAsStringAsync();
  }, TaskScheduler.Default)
  .Unwrap()
  .ContinueWith(task =>
  {
    if (task.IsFaulted)
    {
      tcs.TrySetResult(task.Exception.Flatten().InnerException.ToString());
      return;
    }

    var responseString = task.Result;
    tcs.TrySetResult(responseString);
  }, TaskScheduler.Default);
}

And that's why developers like the async and await keywords. ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM