【问题标题】:Is it safe to call .ConfigureAwait(false) in finally block?在 finally 块中调用 .ConfigureAwait(false) 是否安全?
【发布时间】:2013-02-01 06:16:56
【问题描述】:

我有一个包装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();
            }
        }
    }
}

【问题讨论】:

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


    【解决方案1】:

    .ConfigureAwait(false) 的使用不是问题。 你根本没有在等待任务。因为你不await它,所以await被配置做什么都没关系。

    您所做的只是基本的“一劳永逸”(您可能会接受也可能不会接受)。

    无论如何,您都应该删除ConfigureAwait(false),因为它什么都不做,并且让读者感到困惑。如果您可以发送退出请求但实际上没有退出,那么这没关系。

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

    你不能在finally 块中使用await,但你可以或多或少地通过延续来模仿它的行为。您可能需要执行以下操作:

    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 块时指示它“完成”;不是在延续触发并且对象被处置时。这可能会也可能不会被接受。

    【讨论】:

    • 您已经正确地确定了我的需求。在我处理之前,我需要检查客户端是否仍然登录,如果是,那么我需要至少尝试退出。例如,尝试注销并等待 5 秒,然后无论如何都进行处置……并另外捕获注销尝试的返回值……非常棘手,因为 await 不能在 finally 块中,这对于处置调用来说是“完美的” :)
    • @zam6ak 你基本上必须做await 所做的事情,但没有await。在注销方法上调用ContinueWith,传递取消令牌或使用其他提供超时的机制,然后处理客户端并执行...无论如何...在该延续内注销的结果.
    • 不删除 ConfigureAwait 并执行以下操作怎么样:var signOutAwaitable= restClient.SignOutAsync().ConfigureAwait(false); var signedOutOk = false; signOutAwaitable.GetAwaiter().OnCompleted(() =&gt; signedOutOk = signOutAwaitable.GetAwaiter().GetResult()); Logger.Warn(CultureInfo.InvariantCulture, m =&gt; m("Client was still signed in! Attempt to to sign out was {0}", signedOutOk ? "successful" : "unsuccessful"));
    • 似乎最简单的方法是在 try {} 中等待注销,最后执行“fire and forget”....
    • @zam6ak 再一次,你没有在等待任何东西,所以不要费心使用ConfigureAwait。与使用这些方法相比,直接通过ContinueWith 调用来处理任务会更容易。正如我的回答中所述,您的代码将导致该方法返回的 Task 在该方法真正“完成”之前完成。
    猜你喜欢
    • 1970-01-01
    • 2016-05-17
    • 1970-01-01
    • 2019-08-27
    • 2010-10-05
    • 2015-01-22
    • 1970-01-01
    • 2011-02-18
    相关资源
    最近更新 更多