【问题标题】:Instance variable is null inside Task.RunTask.Run 中的实例变量为空
【发布时间】:2014-10-29 13:53:58
【问题描述】:

更新:

public MobileServiceUser CurrentMsUser { get; private set; } 

我有一个实例属性CurrentMsUser 已填充,我试图在异步方法中引用它,但由于某种原因它在方法中为空。我在属性设置器上设置了一个断点,它永远不会设置为空,所以我相当确定该属性永远不会变为空。在异步方法返回后,它再次出现。似乎一旦我在匿名异步方法中,我就无法访问该属性。这是我的方法:

public async Task CreateOrRetrieveAppUserAsync()
{
    await Task.Run (async () => {
        try {
            var usersCollection = await _userTable.ToCollectionAsync ();
            var users = usersCollection.
                Where(x =>
                    x.FacebookToken == CurrentMsUser.UserId).
                ToList();
            if (users.Count == 1) {
                CurrentRwUser = users [0];
            } else {
                CurrentRwUser = new User { 
                    FacebookToken = CurrentMsUser.UserId, 
                    GoogleToken = "test",
                    TwitterToken = "test",
                    MicrosoftToken = "test",
                    Email = "test@gmail.com",
                    FacebookId = App.FacebookProvider.GetCurrentUserId (),
                    Name = App.FacebookProvider.GetCurrentUserName ()
                };
                await InsertUserAsync (CurrentRwUser);
                await SyncAsync();
            }
        } catch (Exception ex) {
            Debug.WriteLine ("CreateOrRetrieveAppUser failed: {0}", ex.Message);
        }
    }).ConfigureAwait(continueOnCapturedContext:false);
}

【问题讨论】:

  • 出于好奇,您为什么要将所有这些逻辑都包含在一个新任务中?
  • 因为方法是从UI线程调用的
  • 在哪里确切地是空的?似乎这是在设置CurrentRwUser 的值,所以在您调用此方法之前我会假设它为空?你是说它仍然是空的之后分配?你是怎么确定的? InsertUserAsync 中是否为空?如果您在该方法的末尾输入Debug.WriteLine(CurrentRwUser.UserId),它是否会抛出NullReferenceException
  • 它运行的线程应该是无关紧要的。如果长时间运行的操作是异步的,则 UI 将在它们运行时保持响应。
  • 编辑:我的错字。变为空的是 CurrentMsUser,而不是 CurrentRwUser。 @MattBurland - 如果我打破这个方法的左大括号,它就存在,但是一旦我进入 Task.Run 调用中的匿名方法,它就是空的。如果我像您建议的那样运行调试语句,则在方法返回时针对 CurrentMsUser.UserId 它不为空。

标签: c# asynchronous task-parallel-library async-await c#-5.0


【解决方案1】:

该变量似乎是线程本地的,这就是为什么它的值只能在您的主线程中访问。

在移动到后台线程之前,您应该在主线程中获取该变量的值以避免此问题。

public async Task CreateOrRetrieveAppUserAsync()
{
    var userId = CurrentMsUser.UserId;
    await Task.Run (async () => {
    //...
    //use userId here
    });
}

当然,鉴于您似乎没有任何长时间运行的CPU 限制工作,并且您在此处所做的所有工作都已经是基于异步任务的 IO 工作,您应该 能够简单地完全删除Task.Run 调用,允许您 必须在 UI 线程中运行的少量 CPU 绑定工作,而不会因为长时间运行而阻塞它操作都已经是异步的了。

哦,由于您只需要usersCollection 的第一项,因此您应该使用First,而不是ToList,以避免将整个结果集具体化到内存中。您还应该执行First 和您的过滤调用ToCollectionAsync 之前,如果可能的话,通过查询提供程序执行所有这些过滤,而不是将整个集合具体化到您的应用程序中。

【讨论】:

  • 我知道App.FacebookProvider.GetCurrentUserIdApp.FacebookProvider.GetCurrentUserName 是长时间运行的,而不是异步的。或者usersCollection 可能很大。
  • 这有效,但并没有真正回答为什么实例变量首先变为 null。谢谢
  • @DanielMann 它们不是异步的,也不是长时间运行的......只是从已经填充的变量中获取。 usersCollection 也只有大约 20 行..
  • @DanielMann 如果这两种方法实际上运行时间很长,那么它们可以包含在自己的 Task.Run 调用中,尽管我不希望它们如此。至于收藏,请参阅我的编辑。他实际上只是在尝试获取第一项,而为了避免尝试在应用程序端处理整个结果集而进行的微不足道的更改将意味着集合的大小不会影响 CPU 密集型工作。
  • @Servy 现在我很困惑。我刚刚读完 Eric Lippert stackoverflow.com/questions/18770414/… 的这个答案,听起来我应该能够跨线程访问这个属性。如果它是类的公共属性,为什么要让它成为本地线程?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-22
  • 2012-12-31
  • 2015-04-26
  • 1970-01-01
相关资源
最近更新 更多