.NET C#基础(9):资源释放 - 需要介入的资源管理

这篇具有很好参考价值的文章主要介绍了.NET C#基础(9):资源释放 - 需要介入的资源管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 什么是IDisposable?

  IDisposable接口是一个用于约定可进行释放资源操作的接口,一个类实现该接口则意味着可以使用接口约定的方法Dispose来释放资源。其定义如下:
public interface IDisposable
{
    void Dispose();
}
  上述描述中可能存在两个问题:
  1. 什么是“资源”?
  2. C#是一个运行在含有垃圾回收(GC)平台上的语言,为什么还需要手动释放资源?

1.1 资源

  资源包括托管资源和非托管资源,具体来说,它可以是一个普通的类实例,是一个数据库连接,是一个文件指针,或者是一个窗口的句柄等等。不太准确地说,你可以理解为就是程序运行时用到的各种东西。

1.2 为什么要手动释放资源

  对于托管资源,通常来说由于CLR的GC的帮助,可以自动释放回收而无需程序员手动管理。然而,由于C#允许使用非托管资源,这些非托管资源不受GC的控制,无法自动释放回收,因此对于这类资源,就要程序员进行手动管理。另一方面,有些资源虽然是托管资源,但是实际包装了一个非托管资源,并实现了IDispose接口,同样的,对于这类资源,最好也手动管理。
(ps:CLR,Common Language Runtime,即C#编译后的IL代码的运行平台)
(ps:GC,Garbage Collection,垃圾回收,即一种用于自动回收资源的机制)
  如果你写过C++,这就相当于应该在实例销毁时释放掉成“new”出来的分配到堆上的资源,否则资源将一直保留在内存中无法释放,导致内存泄漏等一系列问题。
  在C++中,通常将资源释放的操作放置在类的析构函数中,但C#并没有析构函数这一概念,因此,C#使用IDisposable接口来对资源释放做出约定——当程序员看到一个类实现IDisposable接口时,就应该想到在使用完该类的实例后就应该调用其Dispose方法来及时释放资源。
  对于实现了IDispose接口的类,在C#中你通常可以采用如下方式来释放资源:
  1:try...finally
UnmanagedResource resource = /* ... */;

try
{
    // 各种操作
}
finally
{
    resource.Dispose();
}

(注:在finally中释放是为了确保即便运行时出错也可以顺利释放资源)

  2:using

using (UnmanagedResource resource = /* ... */)
{
    // 离开using的作用域后会自动调用resource的Dispose方法
}

// 或者如果不需要额外控制作用域的简写
using UnmanagedResource resource = /* ... */;
(ps:实际上,哪怕不实现IDisposable接口,只要类实现了public void Dispose()方法都可以使用using进行管理)
(ps:using本质上是try...finally的语法糖,所以即便using块中抛出异常也可以正常释放资源)

 

2. 如何实现IDisposable

2.1 不太完美的基本实现

  你可能还会认为IDisposable很容易实现,毕竟它只有一个方法需要实现,并且看上去只要在方法里释放掉需要释放的资源即可:
class UnmanagedResource : IDisposable
{
    public void Dispose()
    {
        // 释放需要释放的资源
    }    
}
  通常来说这样做也不会有什么大问题,然而,有几个问题需要考虑。接下来将逐步阐述问题并给出解决方案。

2.2 如果使用者忘记了调用Dispose方法释放资源

  尽管程序员都应该足够细心来保证他们对那些实现了Disposable接口的类的实例调用Dispose方法,但是,出于各种原因,或许是他是一名新手,或许他受到老板的催促,或许他昨天没睡好等等,这些都可能导致他没有仔细检查自己的代码。
永远不要假设你的代码会被一直正确地使用,总得留下些兜底的东西,提高健壮性——把你的用户当做一个做着布朗运动的白痴,哪怕他可能是个经验丰富的程序员,甚至你自己。
  对于这样的问题,最自然的想法自然是交给GC来完成——如果程序员忘记了调用Dispose方法释放资源,就留着让GC来调用释放。还好,C#允许你让GC来帮助你调用一些方法——通过终结器。
  关于终结器的主题会是一个比较复杂的主题,因此在这里不展开讨论,将更多的细节留给其他主题。就本文而言,暂时只需要知道终结器的声明方法以及GC会在“某一时刻”自动调用终结器即可。(你或许想问这个“某一时刻”是什么时候,这实际上是需要交给复杂主题来讨论的话题)
  声明一个终结器类似于声明一个构造方法,但是需要在方法的类名前添加一个~。如下:
class UnmanagedResource : IDisposable
{
    // UnmanagedResource的终结器
    ~UnmanagedResource()
    {
        // 一些操作
    }
}
    关于终结器,下面是一些你需要知道的:
    1:一个类中只能定义一个终结器,且终结器不能有任何访问修饰符(即不能添加public/private/protected/internal)
    2:永远不要手动调用终结器(实际上你也无法这么做)
  由于GC会在某一个时刻自动调用终结器,因此如果在终结器中调用Dispose方法,即使有粗心的程序员忘记了手动释放资源,GC也会在某一时刻来帮他们兜底。如下:
class UnmanagedResource : IDisposable
{
    public void Dispose()
    {
        // 释放需要释放的资源
    }  

    ~UnmanagedResource()
    {
        // 终结器调用Dispose释放资源
        Dispose();
    }
}

(ps:你或许会觉得终结器很像C++的析构函数,无论是声明方式还是作用(释放资源)上,但是终结器和析构函数有本质上差别,但这里不展开讨论)

2.3 手动调用了Dispose后,终结器再次调用Dispose

  当你手动调用了Dispose方法后,并不表示你就告诉了GC不要再调用它的终结器,实际上,在你调用Dispose方法后,GC还是会在某一时刻调用终结器,而由于我们在终结器里调用了Dispose方法,这会导致Dispose方法再次被调用——Double Free!
  当然,要解决这一问题非常简单,只需要用一个字段来表明资源是否被释放,并在Dispose方法里检查这个字段的值,一旦发现已经释放则过就立刻返回。如下:
class UnmanagedResource : IDisposable
{
    public void Dispose()
    {
        // 如果已经释放过就立刻返回
        if (_disposed)
        {
            return;
        }
   
        // 释放需要释放的资源
        
        // 标记已释放
        _disposed = true;
    }  

    ~UnmanagedResource()
    {
        Dispose();
    }

    // 用于标记是否已经释放的字段
    private bool _disposed;
}
  这样可以解决资源被重复释放的问题,但是这还是无法阻止GC调用终结器。当然你或许会认为让GC调用终结器没什么问题,毕竟我们保证了Dispose重复调用是安全的。不过,要知道终结器是会影响性能的,因此为了性能考虑,我们还是希望在Dispose方法调用后阻止终结器的执行(毕竟这时候已经不需要GC兜底了)。而要实现这一目标十分简单,只需要在Dipose方法中使用GC.SuppressFinalize(this)告诉GC不要调用终结器即可。如下:
class UnmanagedResource : IDisposable
{
    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }
   
        // 释放需要释放的资源

        _disposed = true;
       
        // 告诉GC不要调用当前实例(this)的终结器
        GC.SuppressFinalize(this);
    }  

    ~UnmanagedResource()
    {
        Dispose();
    }

    private bool _disposed;
}
  这样,如果调用了Dispose方法,就会“抑制”GC对终结器的调用;而让终结器调用Dispose也不会产生什么问题。

2.4 不是任何时候都需要释放所有资源

  考虑一个比较复杂的类:
class UnmanagedResource : IDisposable
{
   // 其他代码
   
    private FileStream _fileStream;
}
  上述例子中,FileStream是一个实现了IDisposable的类,也就是说,FileStream也需要进行释放。UnmanagedResource不仅要释放自己的非托管资源,还要释放FileStream。你或许认为只需要在UnmanagedResourceDispose方法中调用一下FileStreamDispose方法就行。如下:
class UnmanagedResource : IDisposable
{
    // 其它代码    
    
    public void Dispose()
    {
        // 其他代码

        _fileStream.Dispose();

        // 其它代码
    }

    private FileStream _fileStream;
}
  咋一看没什么问题,但是考虑一下,如果UnmanagedResourceDispose方法是由终结器调用的会发生什么?
  提示:终结器的调用是无序的。
  是的,很可能FileStream的终结器先被调用了,执行过了其Dispose方法释放资源,随后UnmanagedResource的终结器调用Dispose方法时会再次调用FileStreamDispose方法——Double Free, Again。
  因此,如果Dispose方法是由终结器调用的,就不应该手动释放那些本身就实现了终结器的托管资源——这些资源的终结器很可能先被执行。仅当手动调用Dispose方法时才手动释放那些实现了终结器的托管资源。
  我们可以使用一个带参数的Dispose方法,用一个参数来指示Dispose是否释放托管资源。稍作调整,实现如下:
class UnmanagedResource : IDisposable
{
    // 其它代码
    private void Dispose(bool disposing)
    {
        // 其他代码
       
        if (disposing)
        {
            // 释放托管资源
            _fileStream.Dispose();
        }
       
        // 释放非托管资源

        // 其它代码
    }
}
  上述代码声明了一个接受disposing参数的Dispose(bool disposing)方法,当disposingtrue时,同时释放托管资源和非托管资源;当disposingfalse时,仅释放托管资源。另外,为了不公开不必要的接口,将其声明为private
  接下来,只需要在Dispose方法和终结器中按需调用Dispose(bool disposing)方法即可。
class UnmanagedResource : IDisposable
{
    // 其它代码

    public void Dispose()
    {
        // disposing=true,手动释放托管资源
        Dispose(true);
        GC.SuppressFinalize(this);
    }    
    
    ~UnmanagedResource()
    {
        // disposing=false,不释放托管资源,交给终结器释放
        Dispose(false);
    }
    
    private void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }
   
        if (disposing)
        {
            // 释放托管资源
        }

        // 释放非托管资源

        _disposed = true;
    }
}

2.5 考虑一下子类的资源释放

  考虑一下如果有UnmanagedResource的子类:
class HandleResource : UnmanagedResource
{
    private HandlePtr _handlePtr;
}
  HandleResource有自己的资源HandlePtr,显然如果只是简单继承UnmanagedResource的话,UnmanagedResourceDispose方法并不能释放HandleResourceHandlePtr
  那么怎么办呢?使用多态,将UnmanagedResourceDispose方法声明为virtual并在HandleResource里覆写;或者在HandleResource里使用new重新实现Dispose似乎都可以:
// 使用多态
class UnmanagedResource : IDisposable
{
    public virtual void Dispose() { /* ... */}
}
class HandleResource : UnmanagedResource
{
    public override void Dispose() { /* ... */}
}


// 重新实现
class UnmanagedResource : IDisposable
{
    public void Dispose() { /* ... */}
}
class HandleResource : UnmanagedResource
{
    public new void Dispose() { /* ... */}
}
  这两种方法似乎都可行,但是一个很大的问题是,你还得对HandleResource重复做那些在它的父类UnmanagedResource做过的事——解决重复释放、定义终结器以及区分对待托管和非托管资源。

  这太不“继承了”——显然,有更好的实现方法。

  答案是:将UnmanagedResource的的Dispose(bool disposing)方法访问权限更改为protected,并修饰为virtual,以让子类访问/覆盖:
class UnmanagedResource : IDisposable
{
    protected virtual void Dispose(bool disposing) { /* ... */ }
}
  这样,子类可以通过覆写Dispose(bool disposing)来实现自己想要的释放功能:
class HandleResource : UnmanagedResource
{
    protected override void Dispose(bool disposing)
    {
        // 其他代码
        
        base.Dispose(disposing);
    }
}
(ps:建议先释放子类资源,再释放父类资源)
  由于Dispose(bool disposing)是虚方法,因此父类UnmanagedResource的终结器和Dispose方法中对Dispose(bool disposing)的调用会受多态的影响,调用到正确的释放方法,故子类可以不必再做那些重复工作。

 

3. 异步释放——IAsyncDisposable

  IAsyncDisposable接口是IDisposable接口的异步版,其设计于处理那些可能造成明显阻塞的清理操作,通过异步释放来避免阻塞。其定义如下:
public interface IAsyncDisposable
{
    ValueTask DisposeAsync();
}
  看起来和IDisposable接口定义基本一致,唯一的区别就是IAsyncDisposable具有异步特征(返回Task/ValueTask,方法名带Async后缀)。
  另外需要注意IAsyncDisposable不是IDisposable的子集(即不包含IDisposable)。不过,尽管IAsyncDisposable不是IDisposable的子集,但是通常的建议是,实现IAsyncDisposable接口的时候应当同时实现IDisposable接口——除非确实无法实现同步释放。

3.1 使用

  IAsyncDisposableIDisposable在使用上基本相似,唯一的区别就是需要加入await关键字。
  1:try...catch
UnmanagedResource res = /* ... */;

try
{
    // 一些操作
}
finally
{
    // 此处添加await等待DisposeAsync操作
    await res.DisposeAsync();
}
  2:using
await using (UnmanagedResource res = /* ... */)
{
    // 一些操作
}

// 或者如果不需要考虑作用域
await using UnmanagedResource res = /* ... */;

3.2 与Disposable重要的区别

3.2.1 终结器不需要异步方法

  由于终结器不支持异步(你无法await),因此在终结器中调用其DisposeAsync方法是不太明智的。当然,直接调用而不等待在某些情况下或许可行,但无论如何都不建议这样做。
  不过,也因为不建议在终结器中调用DisposeAsync,因此实现IAsyncDisposable的时候反而不用像实现IDisposable那样考虑终结器以及因为终结器带来的各种问题。当然,也正因为如此,所以实现IAsyncDisposable接口的时候应当同时实现IDisposable接口,否则如果程序员忘记了调用DisposeAsync方法,就很可能造成资源泄露,因此还是需要一些兜底。

3.2.2 释放什么资源

  你可能认为IAsyncDisposable就是IDisposable的异步版,只是一个异步释放托管/非托管资源,另一个同步释放。然而,真的需要IAsyncDisposable清理非托管资源吗?考虑下面几个问题:
  1:是否存在释放需要大量CPU/IO时间的非托管资源?
  2:这样的非托管资源是否提供了能够用于.NET异步的释放方法?
  对于第一个问题,实际上绝大部分非托管资源无非就是一个指针,绝大部分情况下释放一个指针指向的资源很难成为什么影响性能的操作——考虑释放指针的压力,这在哪怕是在C/C++等这类对性能敏感的语言中也十分罕见。
  对于第二个问题,如果真的就是要释放一个比较复杂的非托管资源,你会发现它也几乎不太可能提供一个可以让你“await”的异步释放方法。Marshal类的各种Free方法也没有异步版——毕竟那压根没必要。
  所以结论是:一般情况下,IAsyncDisposable基本不用考虑释放非托管资源。
(实际就是如果你真的遇到了如此罕见的情况,那么你应该知道该怎么做)
  那么IAsyncDisposable究竟释放什么资源?
  答案是,实现了IAsyncDisposable的托管资源。
class UnmanagedResource : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        await _socket.DisposeAsync();
    }

    // 假设Socket类实现了IAsyncDisposable
    private Socket _socket;
}
  上述代码中UnmanagedResource包含了一个Socket类型的字段,假设Socket类实现了IAsyncDisposable,那么你可以在UnmanagedResourceDisposeAsync中调用Socket的DisposeAsync来释放它。
  当然一个小问题是,也不可能就不清理非托管资源了。答案是:除非你只实现了IAsyncDisposable接口,否则可以交给IDispose实现,毕竟释放这些资源不需要异步操作。

3.3 IAsyncDisposable的实现

3.3.1 简单实现

  上一节中已经给了一个简单的案例,这一节再更详细讨论IAsyncDisposable的实现。类似于IDisposable接口的实现,它的基本结构如下:
class UnamangedResource : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        // 清理(托管)资源
        if (_socket != null)
        {
            await _socket.DisposeAsync();
            _socket = null;  
        }

        // 清理非托管资源(如果有的话)
    }

    private Socket _socket;
}
(这里为了减少代码信息量,不使用空值传播运算符、可空引用类型和is not判空)
  实际上一般来说,上述例子其实就比较完善足以应付一般情况了,唯一需要考虑的只是子类问题(当然,如果类是密封(sealed)类不需要考虑)。
  为了方便子类的调用,类似IDisposable,我们将释放主体实现提取到一个protected virtual方法中,该方法命名为DisposeAsyncCore
class UnamangedResource : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore();
    }
   
    protected virtual async ValueTask DisposeAsyncCore()
    {
        // 清理(托管)资源
        if (_socket != null)
        {
            await _socket.DisposeAsync();
            _socket = null;  
        }

        // 清理非托管资源(如果有的话)
    }

    private Socket _socket;
}
  这样子类就可以通过调用父类的DisposeAsyncCore方法来释放父类的资源,同时只需要覆写DisposeAsyncCore方法就可以实现自己的释放操作:
class HandleResource : UnamangedResource
{
    protected override async ValueTask DisposeAsyncCore()
    {
        // 释放子类资源    
   
        // 调用父类的DisposeAsyncCore释放父类资源
        await base.DisposeAsyncCore();
    }
}

3.3.3 同时实现IDiposable

  由于实现IAsyncDisposable也常常要实现IDisposable,因此实际上文的简单实现是有点缺陷的。暂时先不考虑方法的实现细节,我们仅从方法的抽象上去考虑问题。
class UnamangedResource : IDisposable, IAsyncDisposable
{
    public async ValueTask DisposeAsync();
    
    public void Dispose();
   
    ~UnamangedResource();

    protected virtual async ValueTask DisposeAsyncCore();

    protected void Dispose(bool disposing);
}
  在同时实现IDisposable接口后,有两个问题需要考虑:
  1:DisposeAsyn方法被调用后需要抑制终结器
  2:DisposeAsyncCore不再需要清理非托管资源,这一工作交给Dispose(bool disposing)即可。(前文说过绝大部分情况下非托管资源的清理考虑不上异步)
  所以,更具体的实现如下(只写出IAsyncDisposable的部分,IDisposable的实现细节不变):
class UnamangedResource : IDisposable, IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        // 清理托管资源
        await DisposeAsyncCore();
       
        // disposing=false,只需要让其清理非托管资源
        Dispose(false);
       
        // 同样,抑制终结器
        GC.SuppressFinalize(this);
    }

    public void Dispose();
   
    ~UnamangedResource();

    protected virtual async ValueTask DisposeAsyncCore()
    {
        // 清理托管资源(异步和同步)
    }

    protected void Dispose(bool disposing);
}

 

4. 总结

4.1 代码总览

4.1.1 IDisposable实现

查看代码
 class UnmanagedResource : IDisposable
{
    // 对IDisposable接口的实现
    public void Dispose()
    {
        // 调用Dispose(true),同时释放托管资源与非托管资源
        Dispose(true);
        // 让GC不要调用终结器
        GC.SuppressFinalize(this);
    }    
    
    // UnmanagedResource的终结器
    ~UnmanagedResource()
    {
        // 调用Dispose(false),仅释放非托管资源,托管资源交给GC处理
        Dispose(false);
    }
    
    // 释放非托管资源,并可以选择性释放托管资源,且可以让子类覆写的Dispose(bool disposing)方法
    protected virtual void Dispose(bool disposing)
    {
        // 防止重复释放
        if (_disposed)
        {
            return;
        }
       
        // disposing指示是否是否托管资源
        if (disposing)
        {
            // 释放托管资源
        }

        // 释放非托管资源
        
        // 标记已释放
        _disposed = true;
    }
}

4.1.2 IAsyncDisposable实现

查看代码
 class UnamangedResource : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore();
    }
   
    protected virtual async ValueTask DisposeAsyncCore()
    {
        // 清理(托管)资源
        if (_socket != null)
        {
            await _socket.DisposeAsync();
            _socket = null;  
        }

        // 清理非托管资源(如果有的话)
    }

    private Socket _socket;
}

4.1.3 IAsyncDisposable +IDisposable实现

查看代码
 class UnmanagedResource : IDisposable, IAsyncDisposable
{
    // 对IDisposable接口的实现
    public void Dispose()
    {
        // 调用Dispose(true),同时释放托管资源与非托管资源
        Dispose(true);
        
        // 让GC不要调用终结器
        GC.SuppressFinalize(this);
    }
    
    // 对IAsyncDisposable接口的实现
    public async ValueTask DisposeAsync()
    {
        // 调用DisposeAsyncCore清理托管资源
        await DisposeAsyncCore();
        
        // 调用Dispose(false),仅释放非托管资源
        Dispose(false);
        
        // 让GC不要调用终结器
        GC.SuppressFinalize(this);
    }

    // UnmanagedResource的终结器
    ~UnmanagedResource()
    {
        // 调用Dispose(false),仅释放非托管资源,托管资源交给GC处理
        Dispose(false);
    }
    
    // 释放非托管资源,并可以选择性释放托管资源,且可以让子类覆写的Dispose(bool disposing)方法
    protected virtual void Dispose(bool disposing)
    {
        // 防止重复释放
        if (_disposed)
        {
            return;
        }
       
        // disposing指示是否是否托管资源
        if (disposing)
        {
            // 释放托管资源
        }

        // 释放非托管资源
        
        // 标记已释放
        _disposed = true;
    }
    
    // 异步释放托管资源
    protected virtual async ValueTask DisposeAsyncCore()
    {
        // 清理托管资源(同步和异步)
    }
}

 

参考资料/更多资料:

【1】:IDisposable 接口

【2】:实现 Dispose 方法

【3】:IAsyncDisposable 接口

【4】:实现 DisposeAsync 方法文章来源地址https://www.toymoban.com/news/detail-706349.html

到了这里,关于.NET C#基础(9):资源释放 - 需要介入的资源管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C#析构函数解析:资源管理的精要和使用技巧

      在C#中,析构函数(Destructor)是一个特殊的方法,用于清理对象占用的资源。它是由垃圾回收器在对象被销毁时自动调用的。析构函数的原理是在对象即将被回收时执行一些清理操作,例如释放非托管资源或执行一些对象销毁前的必要操作。 调用时机:  当对象被垃圾回收

    2024年01月24日
    浏览(47)
  • Unity基础笔记(7)—— 资源管理

    一、资源导入和导出 资源导入:将打包好的 unitypackage 文件直接拖拽至窗口中,Unity 会自动解析包,再点击 import 即可导入资源。 资源导出:右键点击要导出的资源文件夹,点击 export 导出资源。预制体身上自带的材质会随着预制体一并导出。 二、预制体实例化 游戏中大多数

    2024年02月16日
    浏览(42)
  • 如何使用Net2FTP轻松部署本地Web文件管理器并远程访问管理内网资源?

    文件传输可以说是互联网最主要的应用之一,特别是智能设备的大面积使用,无论是个人存储文件资料,还是商业文件流转,都有数据存储和传输的需求。有需求就有市场,相应的传输软件层出不穷,而这些软件也各有各的特色,在这些软件的加持下,我们可以轻松的将不方

    2024年02月05日
    浏览(59)
  • Unity基础操作-包与资源管理

     不再像以前一样,会在Unity编辑器内置的浏览器中来打开Unity的网站 并在提示窗口中有给出来解释,是说为了提升编辑器的性能. 并提供了直接到浏览器中访问资源商店的访问链接(Search Online)       因Unity3D编辑器中文包始终未全部完成翻译,具体百度翻译的中英对照情况如下

    2024年02月13日
    浏览(41)
  • 【C#】在Windows资源管理器打开文件夹,并选中指定的文件或文件夹

    因软件里使用了第三方插件,第三方插件的日志文件夹存在路径不止一个,并且可能层级较深。 为便于运维人员和最终用户使用,在界面上增加一个“打开XX文件夹”的按钮,点击时,打开第三方插件日志文件夹所在的上级文件夹,并选中其下级指定名称的若干个文件和文件

    2024年02月14日
    浏览(55)
  • Flutter学习四:Flutter开发基础(五)资源管理

    目录 0 引言 1 资源管理 1.1 指定 assets 1.2 Asset 变体(variant) 1.3 加载 assets 1.3.1  加载文本 1.3.2 加载图片 1.3.2.1 声明分辨率相关的图片 1.3.2.2 加载图片 1.3.3 依赖包中的资源图片  1.3.4 打包包中的 assets 1.3.5  特定平台 assets  1.3.5.1 设置APP图标  1.3.5.2 更新启动页 1.4 平台

    2024年02月11日
    浏览(42)
  • 【Azure】微软 Azure 基础解析(五)核心体系结构之管理组、订阅、资源和资源组以及层次关系

    本系列博文还在更新中,收录在专栏:「Azure探秘:构建云计算世界」 专栏中。 本系列文章列表如下: 【Azure】微软 Azure 基础解析(三)描述云计算运营中的 CapEx 与 OpEx,如何区分 CapEx 与 OpEx 【Azure】微软 Azure 基础解析(四)Azure核心体系结构组件之数据中心、区域与区域

    2024年02月07日
    浏览(56)
  • 人力资源智能化管理项目(day01:基础架构拆解)

    git clone GitHub - PanJiaChen/vue-admin-template: a vue2.0 minimal admin template 项目名 项目模版中的core-js的版本号有些滞后,需要将其版本号改为“3.25.5”(package.json里面18行) 再安装依赖(npm i) 登录界面 首页 ESLint Vetur 提升开发效率 main.js: ①new Vue({})实例化 路由 store(Vuex) 根组件 ②全局注

    2024年01月16日
    浏览(43)
  • 【Go 基础篇】Go语言中的defer关键字:延迟执行与资源管理

    在Go语言中, defer 是一种用于延迟执行函数调用的。它提供了一种简洁而强大的方式,用于在函数返回之前执行一些必要的清理操作或者释放资源。 defer 的灵活性和易用性使得它在Go语言中广泛应用于资源管理、错误处理和代码结构优化等方面。🚀🚀🚀 本篇博客将详

    2024年02月11日
    浏览(53)
  • K8S资源管理之计算资源管理

            以CPU为例,下图显示了未设置Limits与设置了Requests和Limits的CPU使用率的区别        尽管Requests和Limits只能被设置到容器上,但是设置了Pod级别的Requests和Limits能大大提高管理Pod的便利性和灵活性,因此在Kubernetes中提供了对Pod级别的Requests和Limits的配置。对于CP

    2024年04月15日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包