【问题标题】:Xamarin What happens to Lazy initialized singleton after App goes to backgroundXamarin 应用程序进入后台后延迟初始化的单例会发生什么
【发布时间】:2022-01-04 19:39:30
【问题描述】:

我在 Xamarin.Forms 中对应用单例使用延迟初始化(应用在 iOS 上运行):

public sealed class DataSingleton
{
    private static readonly Lazy<DataSingleton> lazy = new Lazy<DataSingleton>(() => new DataSingleton(), LazyThreadSafetyMode.PublicationOnly); // tried withou 2nd parameter as well

    public static DataSingleton Instance
    {
        get { return lazy.Value; }
    }
    ...
}

我在 webserver 中调用它,它运行为 Angular 中的前端提供数据(使用 web 视图显示 Angular 代码)

var server = new WebServer(o => o
                .WithUrlPrefix($"http://localhost:{appSettings.ApiPort}")
                .WithMode(HttpListenerMode.EmbedIO))
            .WithCors()
            .WithLocalSessionManager()
            .WithWebApi("/api", m => m
                .WithController<TestController>()
                .WithController<SettingsController>()
                .WithController<ReportController>()
                .WithController<SystemController>())
            .WithModule(new ActionModule("/", HttpVerbs.Any,
                ctx => ctx.SendDataAsync(new { Message = "Error" })))
            .RunAsync();

在控制器中调用DataSingleton来获取/设置数据,但是app从后台返回后,DataSingleton.Instance为null。

当应用在后台短时间(大约 5 分钟)时,我应该怎么做才能不丢失单例数据

更新 - 我发现这个问题只存在于控制器中,因为当应用程序回到前面时,我可以看到 AppDelegate WillEnterForeground 事件中的所有数据..

【问题讨论】:

  • 很好的问题。恕我直言,移动应用程序的底线是您不应该假设 anything 可以在后台运行然后返回应用程序。 (明确编码的后台服务除外。)您当然不应该依赖任何被保留 5 分钟的东西。不幸的是,这意味着依赖static variables 在后台保存的技术并不可靠。一般建议是将所有用户数据和状态保存到持久存储中,释放所有可能的内容,然后在恢复时,从保存的状态重新初始化所有内容...
  • ... 除非有人有更好的主意,否则您可能必须有初始化代码(每当应用程序从后台返回时),将所有 Lazy 静态变量显式设置为新的 Lazy 实例。或者更好的是,“如果值为空,则创建一个新的懒惰”。我很想知道是否有人对此主题有进一步的见解,以及一般情况下的静态(从背景返回后)!
  • 鉴于您的更新,也许您需要在应用程序进入后台时停止该 WebServer,然后在应用程序返回前台时再次运行相同的代码来创建服务器?
  • @ToolmakerSteve 你是对的! WebServer 是一次性的,所以我必须管理它

标签: ios xamarin xamarin.forms xamarin.ios


【解决方案1】:

鉴于出现问题的是网络服务器,请在应用程序进入后台时停止它。应用返回时重新启动(或根据需要延迟启动)。

代码可能是这样的:

App.xaml.cs:

public static Webserver MyWebServer
{
    get
    {
        if (_server == null)
        {
            _server = new Webserver(...);
        }

        return _server;
    }
}
public static void StopWebServer()
{
    if (_server != null)
    {
        _server.Dispose();
        // So will be created again, on next reference to MyWebServer.
        _server = null;
    }
}
private static Webserver _server;

...

protected override void OnSleep()
{
    StopWebServer();
}

在别处使用:

... App.MyWebServer ...

如果您不想制作静态变量(尽管恕我直言,这对 App 来说是可以的,因为只有一个,并且它的生命周期是应用程序本身的生命周期),然后删除上面的“静态”,在其他地方使用变成:

... (Xamarin.Forms.Application.Current as App).MyWebServer ...

【讨论】:

    【解决方案2】:

    在这种情况下,可能存在竞争条件。 如果两个(或更多线程)第一次同时读取Instance,则会创建多个DataSingleton 实例。但是,其他所有读取都只会获得一个实例。这取决于您的情况,如果可以的话。

    public sealed class DataSingleton {
      private static instance;
    
      // will assign new DataSingleton only if "instance" is null.
      public static Instance => instance ??= new DataSingleton();
    }
    

    或者您可以使用Interlocked 类确保,如果另一个线程已经初始化了instance 字段,instance 字段将不会被覆盖。

    public sealed class DataSingleton {
      private static instance;
      public static Instance {
        get {
          var result = instance;
    
          // early exit if singleton is already initialized
          if (result is not null) return result;
    
          var created = new DataSingleton();
          // thread-safe way how to assign "created" into "instance" only if "instance" refers to null. othervise no assignment will be made
          var original = Interlocked.CompareExchange(ref instance, null, created);
    
          // some other thread already initialized singleton
          if (original is not null) return original;
    
          // return newly created instance
          return result;
        }
      }
    }
    

    或者您可以使用lock 来确保只创建一个实例。

    public sealed class DataSingleton {
      private static instance;
      public static Instance {
        get {
          var result = instance;
    
          // early exit if singleton is already initialized
          if (result is not null) return result;
    
          lock(typeof(DataSingleton)) {
            result = instance;
    
            // double check, if instance was not initialized by another thread
            if (result is not null) return result;
        
            return instance = new DataSingleton();
          }
        }
      }
    }
    

    【讨论】:

    • 您确定这是一个问题吗? Lazy doc 明确指出,默认情况下,它是线程安全的。 docs.microsoft.com/en-us/dotnet/api/system.lazy-1?view=net-6.0 的示例代码演示了每个调用 lazy.Value 的线程 - 与此问题中所做的相同。
    • ...在任何情况下,这几乎肯定与从后台返回的问题无关,这发生在应用程序的主线程上 - 所以多线程不是一个因素。跨度>
    • @ToolmakerSteve 我了解静态字段中的对象丢失并且静态属性返回 null。这是它的解决方案。如果我理解错了,我当然会删除答案。
    • 啊,我明白你的意思了。您正在展示使用 Lazy 的替代方法。在这种情况下,您确实需要处理线程安全。无论如何,您答案的第一句话不适用于问题的原始代码,因为Lazy 为您做到了。 (根据文档 - 我没有亲自验证。)
    • 这种方法没有帮助..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-10
    • 1970-01-01
    • 2014-08-23
    • 2020-08-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多