使用异步方法访问Thread.CurrentPrincipal时出现ObjectDisposedException异常

4

我很新于新的异步/等待功能。然而,我有以下类:

public abstract class PluginBase
{
   //Using EF to store log info to database
   private EFContext _context = new EFContext();
   private int Id = 1;
   protected void LogEvent(string event, string details)
   {
       _context.LogEvents.Add(new LogItem(){
          PluginId = this.Id,
          Event = event,
          Details = details,
          User = Thread.CurrentPrincipal.Identity.Name
       });
   }
 }

public class Plugin : PluginBase
{
   public void Process()
   {
      CallWebService();
   }

   public async void CallWebService()
   {
       using(var http = new HttpClient())
       {
          ...
          var result = await http.PostAsync(memberURI, new StringContent(content, Encoding.UTF8,"application/json"));
          if(result.IsSuccessStatusCode)
             _status = "Success";
          else
             _status = "Fail";

          LogEvent("Service Call - " + _status,...);

       }
}

所以,这个想法是调用Plugin.Process。它又调用CallWebService()。CallWebService发起异步调用http.PostAsync。当我从该调用返回并尝试调用base.LogEvent()时,我会收到一个ObjectDisposedException,指出"Safe Handle has been Closed"。
我知道在等待完成后,该方法的其余代码必须运行。也许它正在某个其他线程或上下文中运行?如果是这种情况,我如何在写入日志时获取当前用户?
感谢您对此的帮助。
编辑 根据Yuval的答案,我做了以下更改,似乎工作正常。
public void Process()
{
    var task = CallWebService();
    task.Wait();
}
public async Task CallWebService(List<Member> members)
{
    using(var http = new HttpClient())
    {
       ...
       using(var result = await http.PostAsync(memberURI, new StringContent content, Encoding.UTF8, "application/json")))
       {
          if(result.IsSuccessStatusCode)
             _status = "Success";
          else
             _status = "Fail";
          LogEvent("Service Call - " + _status,...);
        }
    }
 }

当你使用异步操作时,不要在代码中使用task.Wait()。正确的解决方案是让Processs public Task Process() 并使其返回或等待从CallWebService返回的任务。如果你调用Wait()会很容易导致死锁。如果你想使用await,你必须一直使用它到调用链的最上层。如果不能更改Process来返回任务,则不要使用PostAsync而是使用Post,并且不使用async/await。 - Scott Chamberlain
1个回答

3
当我从调用返回并尝试调用base.LogEvent()时,我会收到一个ObjectDisposedException,指出“安全句柄已关闭”。这是因为在调用链中更高的位置上,有人正在处理您的插件对象,而它实际上还没有完成异步操作。使用async void执行"fire and forget"操作。您实际上没有等待Process,因此任何调用它的人都会认为它已经完成并处置了您的对象。
将您的async void方法更改为async Task,并await它:
public Task ProcessAsync()
{
    return CallWebServiceAsync();
}

public async Task CallWebServiceAsync()
{
   using (var http = new HttpClient())
   {
      var result = await http.PostAsync(memberURI, 
                                        new StringContent(content,
                                                          Encoding.UTF8,
                                                          "application/json"));
      if (result.IsSuccessStatusCode)
         _status = "Success";
      else
         _status = "Fail";

      LogEvent("Service Call - " + _status,...);
   }
}

请注意,您需要在调用栈的更高位置await ProcessAsync

你能帮忙解释一下吗?我在你的CallWebServiceAsync代码中没有看到任务被返回。await会导致任务被返回吗?处理过程能否等待该任务而不是将其进一步传递? - RHarris
我没有看到在你的CallWebServiceAsync中返回任务。该任务是由编译器生成的状态机隐式创建的。如果ProcessAsync已经被等待,那么在CallWebService中使用await将是多余的。 - Yuval Itzchakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接