在finally块中调用.ConfigureAwait(false)是否安全?

时间:2013-01-31 21:57:01

标签: c# async-await c#-5.0

我有一个“rest client”包装HttpClient,其方法是异步的。 除了其他原因,我需要使用我的其他客户端控制登录/注销过程,以便不超过会话数。

其他客户端实现IDisposable,在部署客户端时,我需要检查客户端是否“仍然登录”并注销是否存在。 由于在Dispose方法中进行任何类型的外部调用被认为是不好的做法,我有以下内容

public class MappingsController : RestController
{
    [HttpGet]
    public async Task<HttpResponseMessage> GetYears()
    {
        return await ProcessRestCall(async rc => await rc.GetYearsAsync());
    }
}

public class RestController : ApiController
{
    protected async Task<HttpResponseMessage> ProcessRestCall<T>(Func<RestClient, Task<T>> restClientCallback)
    {
        RestClient restClient = null;
        try
        {
            var credentials = GetCredentialsFromRequestHeader();
            if (credentials == null)
            {
                return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Missing credentials from header!");
            }
            var username = credentials["Username"];
            var password = credentials["Password"];

            restClient = new RestClient(username, password);
            var authenticated = await restClient.SignInAsync();
            if (!authenticated)
            {
                return CreateErrorResponseWithRestStatus(HttpStatusCode.Unauthorized, restClient);
            }
            var result = await restClientCallback(restClient);
            // Following works, but since I need to do it in finally block in case exception happens, perhaps It should be done in finally anyways...
            //await restClient.SignOutAsync(); 
            var response = Request.CreateResponse(HttpStatusCode.OK, result);
            return response;
        }
        catch (Exception e)
        {
            return CreateErrorResponseWithRestStatus(HttpStatusCode.BadRequest, restClient, e);
        }
        finally
        {
            if (restClient != null)
            {
                if (restClient.IsSignedIn)
                {
                    //var signedOutOk = restClient.SignOutAsync();//.Result; //<-- problem - this blocks!!!
                    restClient.SignOutAsync().ConfigureAwait(false); // seems to work, but I am not sure if this is kosher + I can't get return var

                    //Logger.Warn(CultureInfo.InvariantCulture, m => m("Client was still signed in! Attempt to to sign out was {0}", signedOutOk ? "successful" : "unsuccessful"));
                }
                restClient.Dispose();
            }
        }
    }
}

1 个答案:

答案 0 :(得分:4)

.ConfigureAwait(false)的使用不是问题。 您根本没有等待任务。由于您没有await,因此await配置的内容无关紧要。

你所做的只是基本的火灾和忘记(你可能接受或不接受)。

你应该删除ConfigureAwait(false),不管是什么,只因为它什么也不做,让读者感到困惑。如果您可以发送退出请求但实际上没有签出,那么这没关系。

如果您需要确保在退出请求返回之前未调用restClient.Dispose();,那么您有一点......问题。问题源于退出请求可能不成功,或者更糟糕的是,它可能根本不响应。你需要一些方法来处理它。

您无法在await块中使用finally,但您可以通过延续更多或更少地模仿其行为。您可能需要执行以下操作:

public static async Task DoStuff()
{
    IDisposable disposable = null;
    try { }
    finally
    {
        var task = GenerateTask();
        var continuation = Task.WhenAny(task, Task.Delay(5000))
            .ContinueWith(t =>
            {
                if (task.IsCompleted) //if false we timed out or it threw an exception
                {
                    var result = task.Result;
                    //TODO use result
                }

                disposable.Dispose();
            });
    }
}

请注意,由于您未使用await,因此DoStuff返回的任务将在第一次点击finally块时立即显示“已完成”;不是当连续射击并且物体被丢弃时。这可能是也可能是不可接受的。