none
утечка памяти при использовании WPF RRS feed

  • Общие обсуждения

  • Я написал небольшое приложение, чтобы транслировать данные с камеры по сети. Заметил, что на принимающей стороне идет постоянная утечка памяти. Естественно подозрение пало на то, что ресурсы не освобождаются где то, в результате я выбросил все возможное лишнее и локализовал ошибку в одном месте. Дальше моих знаний не хватает, тут явно я чего то не знаю в WPF. Пишу на С# .net 3.5 sp1 WinXP. DirectShow managed.

    public class VideoGrabber : IDisposable, ISampleGrabberCB
    
     {
    
      public event Action<Bitmap> OnSample;
    
    
    
      public void InvokeOnSample(Bitmap buffer)
    
      {
    
       if (null != OnSample)
    
        OnSample(buffer);
    
      }
    
    
    
      private readonly DsDevice _device;
    
      private IFilterGraph2 _gb;
    
      private readonly ICaptureGraphBuilder2 _icgb;
    
      private IMediaControl _mMediaCtrl;
    
      private readonly ISampleGrabber _sg;
    
    #if DEBUG
    
      private DsROTEntry _mRot;
    
    #endif
    
    
    
      public VideoGrabber(DsDevice device)
    
      {
    
       _device = device;
    
       _icgb = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
    
       _gb = (IFilterGraph2)new FilterGraph();
    
       _sg = (ISampleGrabber)new SampleGrabber();
    
    
    
    #if DEBUG
    
       _mRot = new DsROTEntry(_gb);
    
    #endif
    
       var hr = _icgb.SetFiltergraph(_gb);
    
       DsError.ThrowExceptionForHR(hr);
    
      }
    
      ~VideoGrabber() { Dispose(); }
    
    
    
      public void Init()
    
      {
    
       if (null != _mMediaCtrl)
    
        _mMediaCtrl.Stop();
    
    
    
       //source
    
       var fcapture = (IBaseFilter)Marshal.BindToMoniker(_device.DevicePath);
    
       var hr = _gb.AddFilter(fcapture, "Camera");
    
       DsError.ThrowExceptionForHR(hr);
    
       // grabber
    
       var fgrabber = _sg as IBaseFilter;
    
       ConfigSampleGrabber(_sg);
    
       hr = _gb.AddFilter(fgrabber, "Samples Grabber");
    
       DsError.ThrowExceptionForHR(hr);
    
       // null renderer
    
       var fnull = Utils.GetFilterByName(FilterCategory.LegacyAmFilterCategory, "Null Renderer");
    
       hr = _gb.AddFilter(fnull, "Null Renderer");
    
       DsError.ThrowExceptionForHR(hr);
    
    
    
       #region manual connect
    
       // capture pin --> grabber
    
       hr = Utils.ConnectFilters(fcapture, fgrabber, 0, 0, null);
    
       DsError.ThrowExceptionForHR(hr);
    
    
    
       var amt = new AMMediaType();
    
       IEnumPins ppPins;
    
       hr = fcapture.EnumPins(out ppPins);
    
       DsError.ThrowExceptionForHR(hr);
    
       var ppin = new IPin[1];
    
       hr = ppPins.Next(1, ppin, IntPtr.Zero);
    
       var pin = ppin[0];
    
       hr = pin.ConnectionMediaType(amt);
    
       Header = new VideoInfoHeader();
    
       Marshal.PtrToStructure(amt.formatPtr, Header);
    
       DsUtils.FreeAMMediaType(amt);
    
    
    
       // grabber -> null renderer
    
       amt = new AMMediaType { majorType = MediaType.Video };
    
       hr = Utils.ConnectFilters(fgrabber, fnull, 0, 0, amt);
    
       DsError.ThrowExceptionForHR(hr);
    
       DsUtils.FreeAMMediaType(amt);
    
       #endregion
    
       _mMediaCtrl = _gb as IMediaControl;
    
      }
    
      public void Start()
    
      {
    
       if (null == _mMediaCtrl)
    
        return;
    
       var hr = _mMediaCtrl.Run();
    
       DsError.ThrowExceptionForHR(hr);
    
      }
    
      public void Stop()
    
      {
    
       if (null == _mMediaCtrl)
    
        return;
    
       try
    
       {
    
        var hr = _mMediaCtrl.Stop();
    
        DsError.ThrowExceptionForHR(hr);
    
       }
    
       catch { }
    
      }
    
      private void ConfigSampleGrabber(ISampleGrabber sg)
    
      {
    
       var media = new AMMediaType { majorType = MediaType.Video };
    
       sg.SetMediaType(media);
    
       DsUtils.FreeAMMediaType(media);
    
       media = null;
    
       sg.SetCallback(this, 0);
    
      }
    
    
    
      public VideoInfoHeader Header;
    
    
    
      public int SampleCB(double SampleTime, IMediaSample pSample)
    
      {
    
       var bufferLen = pSample.GetActualDataLength();
    
       var buffer = new byte[bufferLen];
    
       IntPtr pBuffer;
    
       var hr = pSample.GetPointer(out pBuffer);
    
       DsError.ThrowExceptionForHR(hr);
    
       Marshal.Copy(pBuffer, buffer, 0, bufferLen);
    
       Marshal.ReleaseComObject(pSample);
    
    
    
       var width = Header.BmiHeader.Width;
    
       var height = Header.BmiHeader.Height;
    
       var stride = width * (Header.BmiHeader.BitCount / 8);
    
       PixelFormat pixelFormat;
    
    
    
       switch (Header.BmiHeader.BitCount)
    
       {
    
        case 24: pixelFormat = PixelFormat.Format24bppRgb; break;
    
        case 32: pixelFormat = PixelFormat.Format32bppRgb; break;
    
        case 48: pixelFormat = PixelFormat.Format48bppRgb; break;
    
        default: throw new Exception("Unknown BitCount");
    
       }
    
    
    
       var cap = new Bitmap(width, height, stride, pixelFormat, pBuffer);
    
    
    
       InvokeOnSample(cap);
    
    
    
       return 0;
    
      }
    
      public int BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen)
    
      {
    
       return 0;
    
      }
    
    
    
      public void Dispose()
    
      {
    
       try
    
       {
    
        if (null != _mMediaCtrl)
    
        {
    
         var hr = _mMediaCtrl.Stop();
    
         DsError.ThrowExceptionForHR(hr);
    
        }
    
       }
    
       catch { }
    
    #if DEBUG
    
       if (_mRot != null)
    
        try
    
        {
    
         _mRot.Dispose();
    
         _mRot = null;
    
        }
    
        catch { }
    
    #endif
    
       if (null == _gb)
    
        return;
    
       Marshal.ReleaseComObject(_gb);
    
       _gb = null;
    
      }
    
     }
    
    

     собственно тут не все красиво и правильно, это в общем то не важно, потом поправлю, хотя, если ошибки тут то с удовольствием поправлюсь

    internal static class Utils
    
     {
    
    
    
      public static IBaseFilter GetFilterByName(Guid category, string name)
    
      {
    
       var devices = DsDevice.GetDevicesOfCat(category);
    
       var moniker = devices.FirstOrDefault(d => d.Name == name);
    
       return null != moniker ? (IBaseFilter)Marshal.BindToMoniker(moniker.DevicePath) : null;
    
      }
    
    
    
      public static int ConnectFilters(IBaseFilter source, IBaseFilter target, int outPinNumber, int inPinNumber, AMMediaType mediatype)
    
      {
    
       IPin pinOutput = null;
    
       IPin pinInput = null;
    
       int hr;
    
    
    
       try
    
       {
    
        pinOutput = DsFindPin.ByDirection(source, PinDirection.Output, outPinNumber);
    
        if (null == pinOutput)
    
         throw new NullReferenceException("OutPin");
    
        pinInput = DsFindPin.ByDirection(target, PinDirection.Input, inPinNumber);
    
        if (null == pinInput)
    
         throw new NullReferenceException("InPin");
    
        hr = pinOutput.Connect(pinInput, mediatype);
    
       }
    
       finally
    
       {
    
        if (null != pinOutput)
    
         Marshal.ReleaseComObject(pinOutput);
    
        if (null != pinInput)
    
         Marshal.ReleaseComObject(pinInput);
    
       }
    
       return hr;
    
      }
    
    
    
      public static BitmapImage BitmapToBitmapImage(Bitmap bitmap)
    
      {
    
       var ms = new MemoryStream();
    
       bitmap.Save(ms, ImageFormat.Jpeg);
    
       var bImg = new BitmapImage();
    
       bImg.BeginInit();
    
       bImg.StreamSource = new MemoryStream(ms.ToArray());
    
       bImg.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
    
       bImg.CacheOption = BitmapCacheOption.None;
    
       bImg.EndInit();
    
       ms.Close();
    
       return bImg;
    
      }
    
    
    
     }
    
    
    
    

    теперь этот обьект использую в WPF приложении:

     public partial class MainWindow
    
     {
    
      public MainWindow()
    
      {
    
       InitializeComponent();
    
       Closing += (o, arg) => { if (null != _grabber) _grabber.Stop(); };
    
      }
    
    
    
      private VideoGrabber _grabber;
    
    
    
      private void Button1Click(object sender, RoutedEventArgs e)
    
      {
    
       if (null != _grabber)
    
        _grabber.Dispose();
    
    
    
       var devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
    
       _grabber = new VideoGrabber(devices[0]);
    
       _grabber.OnSample += GrabberOnSample;
    
       _grabber.Init();
    
       _grabber.Start();
    
      }
    
      public delegate void DelegateRenderImage();
    
      private void GrabberOnSample(Bitmap bitmap)
    
      {
    
       DelegateRenderImage del =
    
        delegate
    
         {
    
          using (bitmap)
    
          {
    
           var source = Utils.BitmapToBitmapImage(bitmap);
    
           var tb = new TransformedBitmap(source, new RotateTransform(180));
    
           if(tb.CanFreeze)
    
            tb.Freeze();
    
           image1.Source = tb; //тут большая проблема. Если закоментарить эту строчку (только её!) утечки памяти нет, проверял в течении 4-5 часов, но если её раскомментарить, 1 гигабайт сьедается очень быстро.
    
          }
    
          bitmap = null;
    
         };
    
    
    
       image1.Dispatcher.BeginInvoke(DispatcherPriority.Normal, del);
    
      }
    
      private void Button2Click(object sender, RoutedEventArgs e)
    
      {
    
       if (null != _grabber)
    
       {
    
        _grabber.Stop();
    
        _grabber.Dispose();
    
       }
    
      }
    
     }
    
    

    как освободить память? я прочитал достаточно много различных статей, но сам ответа не смог найти

    http://blogs.msdn.com/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx

    тут вроде написано как лечить... но что то таблетка не помогает =(

    http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/167c6111-2815-4a77-a79b-6d24ec5bc966

    http://wpfkorea.com/?document_srl=2655

Все ответы

  • Каким образом вы отслеживаете утечку ресурсов и в каком одном месте она локализована?

    В первом приближении я вижу, что метод Dispose() организован плохо. Пустые блоки catch() например могут глушить потенциальные ошибки при попытке остановить IMediaControl.

    Попробуйте отладиться и промониторить это место

  • локализована и обнаружена след образом:

     image1.Source = tb; //тут большая проблема. Если закоментарить эту строчку (только её!) утечки памяти нет, проверял в течении 4-5 часов, но если её раскомментарить, 1 гигабайт сьедается очень быстро.

    да сам граббер я накидал по быстрому, он не идеален но потерь там точно нет, по крайней мере таких огромных. Дело не в нем точно. Я просто один и тот же файл читал в цикле с диска и отправлял его вот сюда: image1.Source = tb; результат тот же.

    Пришлось отказаться от WPF который ИМХО имеет ещё массу косяков. Хотя появились другие косяки с использованием winforms. В частности невозможно создать контрол (не окно) полупрозрачным, можно псевдопрозрачным, но это убивает быстродействие напрочь. зато потери памяти прекратились. ИМХО индусы все таки что то накосячили в реализации. Если уж было так сложно самим собирать мусор грамотно, то можно было бы выставить какой то метод, пусть и опасный для приложения и описать его, что если вы не сделаете того-то и того-то то для вашей программы наступит финальный крэш =)

    Что касается остановки IMediaControl. то да, там есть косяк мой, но я так и не нашел как его пофиксить в менеджед коде.

  • Что значит утечка?

    Если память растет, то это не является утечкой. Просто GC(сборщик мусора) начинает работать только тогда, когда памяти больше нет.

    Для того, чтобы сделать работу программы правильно и очищать память необходима оптимизация кода и вызывать GC.Collect() принудительно.

    Может помочь в понимании работы GC:

    GC в .NET

    8 июля 2010 г. 15:44