Интересные заметки по C# и CLR (v2.0)



    Пришло время выложить вторую часть заметок по .NET.

    Файл конфигурации берет реванш.
    *.CONFIG — действительно можно задавать любому приложению?

    Тыц
    Запускаю известную утилиту от Марка Р. «Procmon.exe», затем свое тестовое оконное приложение и сразу закрываю, останавливаю сбор событий. Фильтрую в полученном логе свое приложение по имени (Include). Вот что там видно:

    1) Поиск файла config:
    22:09:36.0364337	WindowsFormsApplication1.exe	7388	QueryOpen	S:\WindowsFormsApplication1.exe.config	NAME NOT FOUND
    
    2) Поиск файла INI:
    22:09:36.0366595	WindowsFormsApplication1.exe	7388	QueryOpen	S:\WindowsFormsApplication1.INI	NAME NOT FOUND
    
    3) Поиска файла Local:
    22:09:36.0537481	WindowsFormsApplication1.exe	7388	QueryOpen	S:\WindowsFormsApplication1.exe.Local	NAME NOT FOUND
    


    Случайно обнаружил что PowerGUI использует файл конфигурации для скриптов PowerShell которые компилируются в EXE (можно даже запаролить или сразу сделать службу).
    Сами файлы: Untitled.exe и Untitled.exe.config.

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    	<startup useLegacyV2RuntimeActivationPolicy="true">
    		<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
    		<supportedRuntime version="v2.0.50727" />
    	</startup>
      <runtime>
        <loadFromRemoteSources enabled="true"/>
      </runtime>
    </configuration>
    

    .INI — может сообщить JIT-компилятору что сборку не нужно оптимизировать. Значит в Release можно оптимизировать MSIL, а JIT-ом уже управлять через этот файл, не используя два разных билда.

    [.NET Framework Debugging Control]
    GenerateTrackinglnfo = 1
    AllowOptimize = 0
    

    .Local — Dynamic-Link Library Redirection

    Шутка от Джон Роббинса
    Есть еще одно место куда заглядывает процесс.
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\Current Version\Image File Execution Options\

    Создаем раздел реестра MyApp.EXE и внутри него новое строковое значение Debugger, в котором указываем полный путь к отладчику (если бы), пишем calc.exe.

    Теперь при попытке запустить MyApp.EXE на самом деле будет запускаться калькулятор.

    1) Немного истории C#
    Что добавлялось в разных версиях
    C# 2.0
    Обобщения, типы допускающие null, анонимные методы и улучшения с делегатами, итеративные блоки yield. Частичные типы, статические классы, свойства с отличающимися модификаторами доступа, псевдонимы пространств имен (локально — using WinForms = System.Windows.Forms; глобально -FirstAlias::Demo и SecondAlias::Demo), дерективы pragma, буферы фиксированного размера в небезопасном коде (fixed byte data[20]).

    C# 3.0
    LINQ, автоматические свойства, неявная типизация массивов и локальных переменных, инициализаторы объектов и коллекций в месте объявления, анонимные типы. Лямбда выражения и деревья выражений, расширяющие методы, частичные методы.

    C# 4.0
    Именованные аргументы, необязательные параметры, обобщенная вариантность, тип dynamic.

    C# 5.0
    Async/Await, изменение в цикле foreach, атрибуты информации о вызывающем компоненте.


    2) Минимальный размер экземпляра ссылочного типа в памяти.
    Для x86 и x64
    Создаю пустой класс:
    class MyClass { }
    

    Компилирую в 32 бита, узнаю размер в Windbg:
    0:005> !do 023849bc
    Name:        ConsoleApplication1.MyClass
    MethodTable: 006c39d4
    EEClass:     006c1904
    Size:        12(0xc) bytes - вот он размер.
    File:        E:\...\ConsoleApplication1.exe
    Fields:
    None
    

    Компилирую в 64 бита:
    0:003> !do 0000007c8d8465b8
    Name:        ConsoleApplication1.MyClass
    MethodTable: 00007ffa2b5c4320
    EEClass:     00007ffa2b6d2548
    Size:        24(0x18) bytes
    File:        E:\...\ConsoleApplication1.exe
    Fields:
    None
    

    Меньше не будет потому что первые 4 или 8 байт — слово заголовка объекта. Используется для синхронизации, хранения служебной информации сборщика мусора, финализации, хранения хэш-кода. Некоторые биты этого поля определяют, какая информация хранится в нем в каждый конкретный момент времени.
    Вторые 4 или 8 байт — ссылка на таблицу методов.
    Третьи 4 или 8 байт для данных и выравнивания, даже если в них ничего нет.
    Итого минимальный размер экземпляра ссылочного типа для x86 — 12 байт, x64 — 24 байта.


    3) Нестатические поля и методы экземпляра класса в памяти (x64).
    Теперь добавим одно поле и автосвойство
    class MyClass
    {
        private string _field1 = "Some string 1";
        public string Field2 { get; set; }
    }
    

    IL видим два поля:
    .field private string '<Field2>k__BackingField'
    .field private string _field1
    

    И два метода:
    .method public hidebysig specialname instance string get_Field2() cil managed
    .method public hidebysig specialname instance void set_Field2(string 'value') cil managed
    

    Посмотрим кто куда попал:
    0:003> !do 0000005400006600
    Name:        ConsoleApplication1.MyClass
    MethodTable: 00007ffa2b5c4378
    EEClass:     00007ffa2b6d2548
    Size:        32(0x20) bytes
    File:        E:\...\ConsoleApplication1.exe
    Fields:
                  MT    Field   Offset                 Type VT     Attr            Value Name
    00007ffa89d60e08  4000002        8        System.String  0 instance 0000005400006620 _field1
    00007ffa89d60e08  4000003       10        System.String  0 instance 00000054000035a0 <Field2>k__BackingField
    


    Поля попали прямо к экземпляру, и повлияли на его минимальный размер (32 потому что с 17 по 24 бит заняла первая ссылка (ранее были пустыми), а 25-32 вторая (что бы сохранить порядок их следования есть атрибут). Но методов непосредственно в экземпляре нет, только ссылка на них, и соответственно они не повлияли на его размер.

    Посмотрим таблицу методов:
    0:003> !dumpmt -md 00007ffa2b5c4378
    EEClass:         00007ffa2b6d2548
    Module:          00007ffa2b5c2fc8
    Name:            ConsoleApplication1.MyClass
    mdToken:         0000000002000003
    File:            E:\...\ConsoleApplication1.exe
    BaseSize:        0x20
    ComponentSize:   0x0
    Slots in VTable: 7
    Number of IFaces in IFaceMap: 0
    --------------------------------------
    MethodDesc Table
               Entry       MethodDesc    JIT Name
    00007ffa89ae6300 00007ffa896980e8 PreJIT System.Object.ToString()
    00007ffa89b2e760 00007ffa896980f0 PreJIT System.Object.Equals(System.Object)
    00007ffa89b31ad0 00007ffa89698118 PreJIT System.Object.GetHashCode()
    00007ffa89b2eb50 00007ffa89698130 PreJIT System.Object.Finalize()
    00007ffa2b6e0390 00007ffa2b5c4358    JIT ConsoleApplication1.MyClass..ctor()
    00007ffa2b5cc130 00007ffa2b5c4338   NONE ConsoleApplication1.MyClass.get_Field2()
    00007ffa2b5cc138 00007ffa2b5c4348   NONE ConsoleApplication1.MyClass.set_Field2(System.String)
    

    А вот и они, оба еще не прошли JIT-компиляцию, кроме конструктора и унаследованных экземплярных методов от System.Object которые Ngen себя при установке .NET.

    В заключение этого пункта посмотрим полный размер экземпляра с размером объектов на которые указывают его поля:
    MyClass mcClass = new MyClass();
    mcClass.Field2 = "Some string 2";
    
    0:003> !objsize 0000005400006600
    sizeof(0000005400006600) = 144 (0x90) bytes (ConsoleApplication1.MyClass)
    

    Проверим это посмотрев на размер полей:
    0:003> !objsize 0000005400006620
    sizeof(0000005400006620) = 56 (0x38) bytes (System.String)
    0:003> !objsize 00000054000035a0
    sizeof(00000054000035a0) = 56 (0x38) bytes (System.String)
    

    Итого: 56 + 56 + 32 = 144.


    4) Статическое поле и метод (х64).
    Тыц
    class MyClass
    {
        private string _name = "Some string";
        public static string _STR = "I'm STATIC";
        public static void ImStaticMethod() { }
    }
    
    MyClass mcClass = new MyClass();
    Console.WriteLine(MyClass._STR);
    

    Минимальный размер экземпляра (статическое поле не учитывается):
    0:003> !do 00000033ba2c65f8
    Name:        ConsoleApplication1.MyClass
    MethodTable: 00007ffa2b5b4370
    EEClass:     00007ffa2b6c2550
    Size:        24(0x18) bytes
    File:        E:\...\ConsoleApplication1.exe
    Fields:
                  MT    Field   Offset                 Type VT     Attr            Value Name
    00007ffa89d60e08  4000002        8        System.String  0 instance 00000033ba2c6610 _name
    00007ffa89d60e08  4000003       10        System.String  0   static 00000033ba2c35a0 _STR
    

    Список методов:
    0:003> !dumpmt -md 00007ffa2b5b4370
    EEClass:         00007ffa2b6c2550
    Module:          00007ffa2b5b2fc8
    Name:            ConsoleApplication1.MyClass
    mdToken:         0000000002000003
    File:            E:\...\ConsoleApplication1.exe
    BaseSize:        0x18
    ComponentSize:   0x0
    Slots in VTable: 7
    Number of IFaces in IFaceMap: 0
    --------------------------------------
    MethodDesc Table
               Entry       MethodDesc    JIT Name
    00007ffa89ae6300 00007ffa896980e8 PreJIT System.Object.ToString()
    00007ffa89b2e760 00007ffa896980f0 PreJIT System.Object.Equals(System.Object)
    00007ffa89b31ad0 00007ffa89698118 PreJIT System.Object.GetHashCode()
    00007ffa89b2eb50 00007ffa89698130 PreJIT System.Object.Finalize()
    00007ffa2b6d0110 00007ffa2b5b4350    JIT ConsoleApplication1.MyClass..cctor()
    00007ffa2b6d03f0 00007ffa2b5b4348    JIT ConsoleApplication1.MyClass..ctor()
    00007ffa2b5bc130 00007ffa2b5b4338   NONE ConsoleApplication1.MyClass.ImStaticMethod()
    

    ConsoleApplication1.MyClass..cctor() — статический конструктор выполнился только потому что я обратился к статическому полю. Его также называют конструктором типа, и когда он точно вызывается не известно. Он создается автоматически при наличии статических полей. Если ни каких действий производить в нем не нужно то лучше не прописывать его явно, т.к. это помещает оптимизации при помощи флага beforefieldinit в метаданных. Подробней msdn.microsoft.com/ru-ru/library/dd335949.aspx.

    Проверяем размеры:
    0:003> !objsize 00000033ba2c65f8
    sizeof(00000033ba2c65f8) = 72 (0x48) bytes (ConsoleApplication1.MyClass)
    0:003> !objsize 00000033ba2c6610
    sizeof(00000033ba2c6610) = 48 (0x30) bytes (System.String)
    

    Итого: 24 + 48 = 72.
    Статическое поле как и методы, не храниться копией в каждом экземпляре.


    5) Найдем родителя и кто удерживает экземпляр от сборки мусора.
    Для кучи
    Данные и адреса с 3 пункта.
    0:003> !dumpclass 00007ffa2b6c2550
    Class Name:      ConsoleApplication1.MyClass
    mdToken:         0000000002000003
    File:            E:\...\ConsoleApplication1.exe
    Parent Class:    00007ffa89684908
    Module:          00007ffa2b5b2fc8
    Method Table:    00007ffa2b5b4370
    Vtable Slots:    4
    Total Method Slots:  6
    Class Attributes:    100000  
    Transparency:        Critical
    NumInstanceFields:   1
    NumStaticFields:     1
                  MT    Field   Offset                 Type VT     Attr            Value Name
    00007ffa89d60e08  4000002        8        System.String  0 instance           _name
    00007ffa89d60e08  4000003       10        System.String  0   static 00000033ba2c35a0 _STR
    

    Сходим к родителю:
    0:003> !dumpclass 00007ffa89684908
    Class Name:      System.Object
    mdToken:         0000000002000002
    File:            C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
    Parent Class:    0000000000000000 - никого нет.
    Module:          00007ffa89681000
    Method Table:    00007ffa89d613e8
    Vtable Slots:    4
    Total Method Slots:  a
    Class Attributes:    102001  
    Transparency:        Transparent
    NumInstanceFields:   0
    NumStaticFields:     0
    

    Кто удерживает mcClass = new MyClass():
    0:003> !gcroot 00000033ba2c65f8
    Thread 3310:
    00000033b81fedb0 00007ffa2b6d031f ConsoleApplication1.Program.Main
    rbx:
                ->  00000033ba2c65f8 ConsoleApplication1.MyClass
    

    Похоже на правду.


    6) Кто такой Foreach.
    Тыц
    1. При использовании foreach создается скрытая локальная переменная — итератор цикла.

    2. Оператор foreach автоматически вызывает Dispose() в конце цикла, если был реализован интерфейс IDisposable.

    3. Компилируется в вызов методов GetEnumerator(), MoveNext() и обращение к свойству Current.

    4. Foreach как и yield return, LINQ — ленивая итерация, очень полезна когда например читаем мгогогигабайтный файл по одной строке за раз, экономя память.

    5. Foreach для массива использует его свойство Length и индексатор массива, а не создает объект итератора.

    6. В C# 5 захваченные переменные внутри циклов foreach теперь отрабатывают правильно, в то время как C# 3 и C# 4 захватят лишь один экземпляр переменной (последний).


    7) LINQ
    Клац
    1. LINQ to Object исполняется в JIT как обычные делегаты (внутрипроцессный запрос), LINQ to SQL строит дерево выражений и выполняет его уже SQL, или любая другая среда. Дерево может быть переведено в делегаты.

    2. OrderBy для LINQ to Objects требует загрузки всех данных.

    3. При использовании Join() в LINQ to Objects правая последовательность буферизируется, но для левой организуется поток, поэтому если нужно соединить крупную последовательность с мелкой, то полезно по возможности указывать мелкую последовательность как правую.

    4. EnumType.Select( x => x ) — это называется вырожденным выражением запроса, результатом является просто последовательность элементов а не сам источник, это может быть важно с точки зрения целостности данных. (Справедливо для правильно спроектированных поставщиков данных LINQ.)


    8) Коллекции.
    Клик
    List T — внутренне хранит массив. Добавление нового элемента это либо установление значения в массиве, либо копирование существующего массива в новый, который в два раза больше (недокументированно) и только потом установке значения. Удаление элемента из List T требует копирования расположенных за ним элементов на позицию назад. По индексу RemoveAt() удалять значительно быстрее чем по значению Remove() (происходит сравнение каждого элемента где бы он не находился).

    Массивы всегда фиксированы по размеру, но изменяемы в терминах элементов.

    LinkedList T — связанный список, позволяет быстро удалять, вставлять новые элементы, нет индекса, но проход по нему остается эффективным.

    ReadOnlyDictionary — просто оболочка, которая скрывает все изменяемые операции за явной реализацией интерфейса. Можно изменять элементы через переданную в ее основу коллекцию.


    9) Необязательные параметры метода.
    Спойлер!
    void Method1( int x ) { x = 5; }
    IL:
    .method private hidebysig instance void  Method1(int32 x) cil managed
    {
      // Code size       4 (0x4)
      .maxstack  8
      IL_0000:  ldc.i4.5
      IL_0001:  starg.s    x
      IL_0003:  ret
    } // end of method TestClass::Method1
    
    void Method ( int x = 5 ) { }
    IL:
    .method private hidebysig instance void  Method([opt] int32 x) cil managed
    {
      .param [1] = int32(0x00000005)
      // Code size       1 (0x1)
      .maxstack  8
      IL_0000:  ret
    } // end of method TestClass::Method
    

    int x — это константа. А константы хранятся напрямую в метаданных, и значит что для их изменения надо перекомпилировать все использующие этот метод код. (2 источник, страница 413.)

    Начиная с C# 4 переименование параметров метода может повлиять на другой код, если в нем использовались именованные аргументы.


    10) Оптимизация приложений на платформе .NET
    hacer clic
    1. Счетчики производительности.
    Счетчики обновляются не чаще нескольких раз в секунду, а сам Performance Monitor не позволяет читать значения счетчиков чаще, чем один раз в секунду.

    2. Event Tracing for Windows ETW.
    Это высокопроизводительный фреймворк регистрации событий.

    Читают события от ETW:
    а) Windows Performance Toolkit.
    б) PerfMonitor. (открытый проект команды CLR в Microsoft.)
    в) PerfView. (бесплатный комбайн от Microsoft.)

    3. Профилировщик памяти (помимо встроенного в VS) CLR Profiler.
    Может подключаться к существующим процессам (CLR не ниже 4.0) или стартовать новые, собирает все события выделения памяти и сборки мусора. Строит кучу графиков.

    Общие шаблоны для неправильно работающих многопоточных приложений.


    11) Синхронизация.
    Тык
    lock ( obj ) { }
    

    Выполняется только по требованию, затратна по времени. CLR создает структуру «sync block» в глобальном массиве «sync block table», она имеет обратную ссылку на объект владеющий блокировкой по слабой ссылке (для возможности утилизации) и еще ссылку на monitor реализованном на событиях Win32. Числовой индекс блока синхронизации храниться в слове заголовка объекта. Если объект синхронизации был утилизирован, то его связь с блоком синхронизации затирается для возможности повторного использования на другом объекте.

    Но не все так просто, существует еще тонкая блокировка (thin lock). Если блок синхронизации еще не создан и только один поток владеет объектом, то другой при попытке выполнения будет ждать короткое время когда информация о владельце исчезнет из слова заголовка объекта, если этого не произойдет то тонкая блокировка будет преобразована в обычную.


    12) Упаковка.
    Пакет ннада
    Имеем структуру:
    public struct Point
    {
        public int X;
        public int Y;
    }
    
    List<Point> polygon = new List<Point>();
    for ( int i = 0; i < 10000000; i++ )
    {
        polygon.Add( new Point() { X = rnd.Next(), Y = rnd.Next() } );
    }
    
    Point point = new Point { X = 5, Y = 7 };
    bool contains = polygon.Contains( point );
    

    Производим запуск № 1.

    Теперь добавим методы:
    public override int GetHashCode()
    {
        return (X & Y) ^ X; // для теста.
    }
    
    public override bool Equals( object obj )
    {
        if ( !( obj is Point ) ) return false;
        Point other = ( Point ) obj;
        return X == other.X && Y == other.Y;
    }
    
    public bool Equals( Point other )
    {
        return X == other.X && Y == other.Y;
    }
    

    Производим запуск № 2.

    Теперь добавим реализацию интерфейса (подходящий метод уже есть):
    public struct Point : IEquatable<Point>
    { ... }
    

    Производим запуск № 3.
    (List T не имеет реализации интерфейса IEquatable T )

    Пробуем анонимный тип:
    var someType = new { Prop1 = 2, Prop2 = 80000 };
    
    var items = Enumerable.Range( 0, 10000000 )
                       .Select( i => new { Prop1 = i, Prop2 = i+i } )
                       .ToList();
    items.Contains(someType);
    

    Производим запуск № 4.
    Компилятор выяснил что тип someType идентичен типу в методах расширения, и поэтому проблем не возникло.

    Итоги
    Результаты тестов:


    А вот как выглядит анонимный тип в IL:


    Если интересно, как выглядит someType в памяти
    var someType = new { Prop1 = 2, Prop2 = 80000 };
    
    0:005> !do 0000008da2745e08
    Name:        <>f__AnonymousType0`2[[System.Int32, mscorlib],[System.Int32, mscorlib]]
    MethodTable: 00007ffa2b5b4238
    EEClass:     00007ffa2b6c2548
    Size:        24(0x18) bytes
    File:        E:\...\BoxingUnboxingPointList.exe
    Fields:
                  MT    Field   Offset                 Type VT     Attr            Value Name
    0...0  4000003        8         System.Int32  1 instance                2 <Prop1>i__Field
    0...0  4000004        c         System.Int32  1 instance            80000 <Prop2>i__Field
    

    Тип значения хранит самое значение — 2 и 80000.

    Таблица методов:
    0:005> !dumpmt -md 00007ffa2b5b4238
    EEClass:         00007ffa2b6c2548
    Module:          00007ffa2b5b2fc8
    Name:            <>f__AnonymousType0`2[[System.Int32, mscorlib],[System.Int32, mscorlib]]
    mdToken:         0000000002000004
    File:            E:\...\BoxingUnboxingPointList.exe
    BaseSize:        0x18
    ComponentSize:   0x0
    Slots in VTable: 7
    Number of IFaces in IFaceMap: 0
    --------------------------------------
    MethodDesc Table
               Entry       MethodDesc    JIT Name
    0...8 0...0   NONE <>f__AnonymousType0`2[[...]].ToString()
    0...0 0...8   NONE <>f__AnonymousType0`2[[...]].Equals(System.Object)
    0...8 0...0   NONE <>f__AnonymousType0`2[[...]].GetHashCode()
    0...0 0...0 PreJIT System.Object.Finalize()
    0...0 0...8   NONE <>f__AnonymousType0`2[[...]]..ctor(Int32, Int32)
    0...8 0...0   NONE <>f__AnonymousType0`2[[...]].get_Prop1()
    0...0 0...8   NONE <>f__AnonymousType0`2[[...]].get_Prop2()
    

    Я тоже ожидал увидеть другое :)


    13) Async/Await
    Внутри он больше
    Async — не имеет представления в сгенерированном коде.
    Await — конечный автомат, структура. Если к моменту встречи этого типа результат работы уже будет доступен, то метод продолжит работу с полученным результатом в синхронном режиме. Метод Task TResult.ConfigureAwait со значением true попытается выполнить маршалинг продолжения обратно в исходный захваченный контекст, если этого не требуется используем значение false.

    Отличный бесплатный видео урок на эту тему от Александра.

    Также очень хорошо прочитать перевод статьи "SynchronizationContext — когда MSDN подводит".


    14) Сборка мусора
    Тык
    Если играться с закрепленными объектами в поколении «0», то CLR может объявить это поколение более старшим, а себе выделить новое.

    В поколение «1» можно попасть только из «0», объекты имеющие финализацию точно окажутся в нем.

    Размер поколения «2» не ограничивается искусственно, используется вся доступная память (которой Windows может поделиться с CLR), но GC не ждет ее полного заполнения а использует пороговые значения (какие не знаю).

    На фазе маркировки объект помеченный как живой может потерять свою ссылку, тем самым пережив одну сборку.

    В Large Object Heap попадают объекты больше 85 кбайт, но это относится к одному объекту а не его графу (включенные объекты). Связан с поколением «2», собираются вместе.


    Источники:
    1) Джон Роббинс «Отладка приложений для Microsoft .NET и Microsoft Windows».
    2) Джон Скит «C# для профессионалов.Тонкости программирования», 3 издание.
    3) Саша Голдштейн, Дима Зурбалев, Идо Флатов «Оптимизация приложений на платформе .Net».

    Много букв получилось, JIT-компилятор остается на потом.
    Спасибо за внимание.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 30

      +4
      >> Отличный бесплатный видео урок на эту тему от Александра.
      «Вы, как начинающие программисты...»
      «Почему они назвали эту машину „Машиной состояний“ вам сейчас будет пока непонятно, но подождите, я сейчас всё объясню»
      и т.д.
      Как же мне не нравятся презентаторы, считающие себя умнее других… Если честно, он напоминает того самого брата из фильма Брат 2, не в обиду будет сказано :)
        +2
        Через Task TResult.ConfigureAwait можно управлять в каком именно потоке должен вернуться результат работы.

        Если вкратце, то нет, нельзя.

        Возьметесь доказать свое утверждение?
          –1
          Отчасти так, детали не документированы явно. Передавая FALSE мы лишь сообщим что на с не заботит где выполниться продолжение, а не использовать специфический контекст.
            +1
            Вообще не так.

            Когда вы передаете false вы говорите, что система не должна пытаться вернуть выполнение в контекст. Когда вы передаете true (или, что эквивалентно, не вызываете метод), система будет пытаться вернуть вас в контекст, который был на момент запуска метода (если какой-то был), что в определенных случаях приводит к дедлокам.

            Но, что важно: это все не имеет никакого отношения к потокам, только к контексту. Яркий пример — asp.net, где выполнение скачет между потоками как угодно, но движок следит за тем, чтобы весь конекст сохранялся. Только на этой неделе отлавливали связанные с этим баги.
              0
              Согласен что сложно, именно поэтому «По разному ведет себя в консолях, WinForms, WPF…».
              Не имеет отношения к потокам? Тогда зачем Task и пул.
              Не мешайте все в одну кучу. Если вы очень хорошо знаете работу этого типа в контексте aps.net, то это не распространяется в равной степени на другие его формы.
                +2
                Согласен что сложно, именно поэтому «По разному ведет себя в консолях, WinForms, WPF…».

                На самом деле, формально — нет. Ведет себя строго одинаково — возвращает (или нет) на контекст. Это контексты отличаются.

                Не имеет отношения к потокам? Тогда зачем Task и пул.

                Пула там (явно) нет, есть только таск. А таск там именно затем, чтобы абстрагироваться от потоков. Task, async/await, вообще TPL — это абстракции более высокого порядка, нежели потоки.

                Если вы очень хорошо знаете работу этого типа в контексте aps.net, то это не распространяется в равной степени на другие его формы.

                Это не работа контекста, это работа рантайма. Он и без контекста себя так ведет.
                  –3
                  «Люди видят то, что хотят видеть; слышат то, что хотят слышать; верят в то, во что хотят верить и отказываются верить в то, что им не нравится.» Скилеф

                  Игра слов, вы безусловно правы, согласен с вами.
                    +3
                    Вы считаете разницу между потоком и контекстом — игрой слов?

                    Казалось бы, простой пример:

                    Thread.CurrentPrincipal = GePrincipal();
                    if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
                    {
                        await DoSomeWork(); //continuation will run on captured context
                        Debug.Assert(Thread.CurrentPrincipal.Identity.IsAutenticated); //WTF?!
                    }
                    
                      –2
                      Вы серьезно?
                      В контексте asp.net имеется ввиду сам asp.net.
                        +2
                        Я совершенно серьезно, потому что везде в этой дискуссии я имел в виду другой контекст.
                          –2
                          Согласен с вами, в asp.net поведение отличается от консольного или того же WinForms.

                          Именно для этого в топике предложение:
                          «По разному ведет себя в консолях, WinForms, WPF…», можно дописать asp.net, wcf и т.д.

                          Но изучить и описать в одном пункте сразу все возможные повороты я не могу, тем более что по словам Джона Скита «информация не документирована явно». И когда это средство впервые было представлено публике Хейлсбергом произошло много изменений, и не факт что их не будет в дальнейшем (без нарушения работы существующих решений).

                          Ваше замечание уместно и справедливо, спасибо что подсказали как оно в asp.net.

                          P.S. Два года назад я проходил курс внутреннего устройства Windows NT, и думаю что догадываюсь что такое контекст потока.
                            +2
                            Согласен с вами, в asp.net поведение отличается от консольного или того же WinForms.

                            Да нет же. Поведение await везде одинаково — захват контекста, и если тот существует, то continuation будет отправлен на него, а если нет — выполнен в произвольном контексте. Именно поэтому я сразу сказал, что управлять потоком вы не можете никак (разве что вы решите написать свой собственный шедулер/контекст).

                            Отличается только поведение контекстов синхронизации (и оно в среднем — за исключением консольных приложений — реализовано весьма логично).
                              0
                              Извиняюсь, что влезаю не в тему, но судя по всему, тут собрались спецы в async/await-ах :) Мы в своём проекте используем Reactive Extensions, и в этом фреймворке довольно гибко реализована возможность написания юнит-тестов: можно ввести виртуальное время и исполнять задачи на специальном TestScheduler-е. А как с этим обстоит в TPL? То, что я нагуглил, оставляет довольно грустное впечатление.
                                +1
                                Я не видел никаких специальных расширений для тестирования времени в TPL. К сожалению.
                                  0
                                  Спасибо, похоже на правду. (Ответил сам себе случайно. Должно было быть lair )
                                0
                                Слава богу, разобрались.
                                  0
                                  Вот только в посте у вас все так же написано ложное утверждение.
                                    0
                                    Исправил, спасибо.
                                    Думал одно, писал другое.
                                    Оказывается не документирована именно работа метода со значением FALSE.
                    0
                    По разному ведет себя в консолях, WinForms, WPF…

                    это вообще как?

                    таски очень завязаны на SyncronizationContext (о котором говорит товарищ lair).

                    Пишите ли Вы простую либу с асинхронным методом, будьте любезны использовать ConfigureAwait(false).
                    Вне зависимости от предназначения вашего кода.

                    WPF, WinForms, ASP.NET, XYZ — все работает абсолютно идентично в вопросе async/await.
              +2
              Размер поколения «2» не ограничивается искусственно, используется вся доступная память (которой Windows может поделиться с CLR), но GC не ждет ее полного заполнения а использует пороговые значения (какие не знаю).

              Цитата из «Under the Hood of .NET Management» от Red-Gate:
              The GC runs automatically on a separate thread under one of the conditions below.
              • When the size of objects in any generation reaches a generation-specific threshold. To
              be precise, when:
              • Gen 0 hits ~256 K
              • Gen 1 hits ~ 2 MB (at which point the GC collects Gen 1 and 0)
              Gen 2 hits ~10 MB (at which point the GC collects Gen 2, 1 and 0)
              • GC.Collect() is called in code
              • the OS sends a low memory notification.
                0
                У меня есть эта книжечка, спасибо что напомнили про нее.

                В разных источниках разные данные:
                Поколение 0 — изначально отводится от 256 Кбайт до 4 Мбайт, далее может меняется динамически. Он зависит от разрядности ОС, размера кэш памяти процессора L2 и L3.

                Поколение 1 — немногим больше поколения 0.

                Поколение 2 — не ограничен искусственно. 10 mb наверное первое пороговое значение, можно проверить в отладчике размер куч если интересно.

                * CLR резервирует блоки памяти (запрашивает у Windows), но не выдает их процессу сразу и полностью. Блока два, один SOH другой LOH, и с ними тоже есть свои тонкости и настройки (подробней в 3 источнике).
                  0
                  Что бы убрать сомнения и разобраться самому, приведу результаты тестов консольного приложения в котором создается 10 млн (+1) Point и затем сразу 10 млн (+1) анонимных типов. Сбор данных идет до запуска циклов и после.

                  До
                  0:003> !eeheap -gc
                  Number of GC Heaps: 1
                  generation 0 starts at 0x000000c200001030
                  generation 1 starts at 0x000000c200001018
                  generation 2 starts at 0x000000c200001000
                  ephemeral segment allocation context: none
                   segment     begin allocated  size
                  000000c200000000  000000c200001000  000000c200005fe8  0x4fe8(20456)
                  Large object heap starts at 0x000000c210001000
                   segment     begin allocated  size
                  000000c210000000  000000c210001000  000000c210009728  0x8728(34600)
                  Total Size:              Size: 0xd710 (55056) bytes.
                  ------------------------------
                  GC Heap Size:            Size: 0xd710 (55056) bytes.
                  
                  
                  0:003> !heapstat
                  Heap             Gen0         Gen1         Gen2          LOH
                  Heap0           20408           24           24        34600
                  
                  Free space:                                                 Percentage
                  Heap0              24            0            0          152SOH:  0% LOH:  0%
                  



                  После
                  0:006> !eeheap -gc
                  Number of GC Heaps: 1
                  generation 0 starts at 0x000000c20e416a70
                  generation 1 starts at 0x000000c20de16a40
                  generation 2 starts at 0x000000c200001000
                  ephemeral segment allocation context: none
                   segment     begin allocated  size
                  000000c200000000  000000c200001000  000000c20e4f9fe8  0xe4f8fe8(240095208)
                  Large object heap starts at 0x000000c210001000
                   segment     begin allocated  size
                  000000c210000000  000000c210001000  000000c210009728  0x8728(34600)
                  000000c218000000  000000c218001000  000000c220001040  0x8000040(134217792)
                  Total Size:              Size: 0x16501750 (374347600) bytes.
                  ------------------------------
                  GC Heap Size:            Size: 0x16501750 (374347600) bytes.
                  
                  
                  0:006> !heapstat
                  Heap             Gen0         Gen1         Gen2          LOH
                  Heap0          931192      6291504    232872512    134252392
                  
                  Free space:                                                 Percentage
                  Heap0              24           24        69984          184SOH:  0% LOH:  0%
                  



                  Gen2 232872512 — 222 Mb.
                  Данные проверял в Windbg и Process Explorer, консольное приложение собрано в режиме x64.

                  Оборудование:
                  Windows 8.1 64, i7-4702MQ ( L2 — 4x 256 Kb, L3 — 6 Mb ).
                    0
                    В принципе, цитата и не утверждает, что Gen 2 ограничен размером ~10MB. Это очевидно. Гораздо более интересно, что означает ~10MB при неограниченном размере Gen 2. В книге подробной информации по этому поводу нет.
                    Я предполагаю, что это не первый порог, а итеративное значение суммы масс всех объектов, перешедших с Gen 1 в Gen 2 с момента последнего запуска Full GC collects (Gen 2, 1 and 0). Если эта сумма равна или больше ~10MB, то запускается полная сборка мусора, а значение суммы масс обнуляется. И так далее. Но это лишь мои догадки.
                      0
                      Согласен с вами. Я цитату прочитал внимательно уже когда тесты произвел, и понял что вы имели ввиду совсем другое.

                      P.S. Gen 0 = почти равен L2; Gen 1 = L3. Но об этом потом.
                0
                1. LINQ to Object исполняется в JIT как обычные делегаты (внутрипроцессный запрос), LINQ to SQL строит дерево выражений и выполняет его уже SQL, или любая другая среда. Дерево может быть переведено в делегаты.

                Не все так просто :)

                LINQ to SQL — есть тот же самый LINQ to Everything, правда подставляется свой провайдер IQueryable. исполняет его CLR (ваш K.O).
                Дерево переводится в таких случаях для делегаты с переписыванием самого дерева (привет CompiledQuery).
                SQL генерируется и передается в ADO.NET или xyz.

                Поэтому всегда можно создать LINQ to anything.
                А так, да, дерево выражений всегда можно перевести в делегат через LambdaExpression.
                  0
                  Про сборку мусора… эх двумя словами и не скажешь.

                  Так массив из System.Double при размере 10 600 элементов (85000 / 8 байт) должен попасть в LOH. Однако это происходит уже при размере 1000+.
                    0
                    Вы число точно написали, или это шутки такие :)

                    10 000
                    0:003> !eeheap -gc
                    Number of GC Heaps: 1
                    generation 0 starts at 0x0000002a13ec1030
                    generation 1 starts at 0x0000002a13ec1018
                    generation 2 starts at 0x0000002a13ec1000
                    ephemeral segment allocation context: none
                     segment     begin allocated  size
                    0000002a13ec0000  0000002a13ec1000  0000002a13edb898  0x1a898(108696)
                    Large object heap starts at 0x0000002a23ec1000
                     segment     begin allocated  size
                    0000002a23ec0000  0000002a23ec1000  0000002a23ec9728  0x8728(34600)
                    Total Size:              Size: 0x22fc0 (143296) bytes.
                    ------------------------------
                    GC Heap Size:            Size: 0x22fc0 (143296) bytes.
                    
                    0:003> !heapstat
                    Heap             Gen0         Gen1         Gen2          LOH
                    Heap0          108648           24           24        34600
                    
                    Free space:                                                 Percentage
                    Heap0              24            0            0          152SOH:  0% LOH:  0%
                    
                    0:003> !dumpheap 0x0000002a23ec1000
                             Address               MT     Size
                    0000002a23ec1000 0000002a11e9b380       24 Free
                    0000002a23ec1018 0000002a11e9b380       30 Free
                    0000002a23ec1038 00007ffefe1b4918     8848     
                    0000002a23ec32c8 0000002a11e9b380       30 Free
                    0000002a23ec32e8 00007ffefe1b4918     1056     
                    0000002a23ec3708 0000002a11e9b380       30 Free
                    0000002a23ec3728 00007ffefe1b4918     8192     
                    0000002a23ec5728 0000002a11e9b380       30 Free
                    0000002a23ec5748 00007ffefe1b4918    16352     
                    
                    Statistics:
                                  MT    Count    TotalSize Class Name
                    0000002a11e9b380        5          144      Free
                    00007ffefe1b4918        4        34448 System.Object[]
                    Total 9 objects
                    
                    0:003> !dumpheap -type System.Double[]
                             Address               MT     Size
                    0000002a13ec6a08 00007ffefe218450    80024     
                    
                    Statistics:
                                  MT    Count    TotalSize Class Name
                    00007ffefe218450        1        80024 System.Double[]
                    Total 1 objects
                    
                    0:003> !do 0000002a13ec6a08
                    Name:        System.Double[]
                    MethodTable: 00007ffefe218450
                    EEClass:     00007ffefdc05138
                    Size:        80024(0x13898) bytes
                    Array:       Rank 1, Number of elements 10000, Type Double
                    Fields:
                    None
                    



                    11 000
                    0:003> !eeheap -gc
                    Number of GC Heaps: 1
                    generation 0 starts at 0x000000ef228f1030
                    generation 1 starts at 0x000000ef228f1018
                    generation 2 starts at 0x000000ef228f1000
                    ephemeral segment allocation context: none
                     segment     begin allocated  size
                    000000ef228f0000  000000ef228f1000  000000ef228f7fe8  0x6fe8(28648)
                    Large object heap starts at 0x000000ef328f1000
                     segment     begin allocated  size
                    000000ef328f0000  000000ef328f1000  000000ef3290ef20  0x1df20(122656)
                    Total Size:              Size: 0x24f08 (151304) bytes.
                    ------------------------------
                    GC Heap Size:            Size: 0x24f08 (151304) bytes.
                    
                    0:003> !heapstat
                    Heap             Gen0         Gen1         Gen2          LOH
                    Heap0           28600           24           24       122656
                    
                    Free space:                                                 Percentage
                    Heap0              24            0            0          184SOH:  0% LOH:  0%
                    
                    0:003> !dumpheap 0x000000ef328f1000
                             Address               MT     Size
                    000000ef328f1000 000000ef2090b830       24 Free
                    000000ef328f1018 000000ef2090b830       30 Free
                    000000ef328f1038 00007ffefe1b4918     8848     
                    000000ef328f32c8 000000ef2090b830       30 Free
                    000000ef328f32e8 00007ffefe1b4918     1056     
                    000000ef328f3708 000000ef2090b830       30 Free
                    000000ef328f3728 00007ffefe1b4918     8192     
                    000000ef328f5728 000000ef2090b830       30 Free
                    000000ef328f5748 00007ffefe1b4918    16352     
                    000000ef328f9728 000000ef2090b830       30 Free
                    000000ef328f9748 00007ffefe218450    88024     
                    
                    Statistics:
                                  MT    Count    TotalSize Class Name
                    000000ef2090b830        6          174      Free
                    00007ffefe1b4918        4        34448 System.Object[]
                    00007ffefe218450        1        88024 System.Double[]
                    Total 11 objects
                    
                    0:003> !dumpheap -type System.Double[]
                             Address               MT     Size
                    000000ef328f9748 00007ffefe218450    88024     
                    
                    Statistics:
                                  MT    Count    TotalSize Class Name
                    00007ffefe218450        1        88024 System.Double[]
                    Total 1 objects
                    
                    0:003> !do 000000ef328f9748
                    Name:        System.Double[]
                    MethodTable: 00007ffefe218450
                    EEClass:     00007ffefdc05138
                    Size:        88024(0x157d8) bytes
                    Array:       Rank 1, Number of elements 11000, Type Double
                    Fields:
                    None
                    



                    10 050
                    0:003> !eeheap -gc
                    Number of GC Heaps: 1
                    generation 0 starts at 0x0000009146561030
                    generation 1 starts at 0x0000009146561018
                    generation 2 starts at 0x0000009146561000
                    ephemeral segment allocation context: none
                     segment     begin allocated  size
                    0000009146560000  0000009146561000  000000914657ba28  0x1aa28(109096)
                    Large object heap starts at 0x0000009156561000
                     segment     begin allocated  size
                    0000009156560000  0000009156561000  0000009156569728  0x8728(34600)
                    Total Size:              Size: 0x23150 (143696) bytes.
                    ------------------------------
                    GC Heap Size:            Size: 0x23150 (143696) bytes.
                    
                    0:003> !heapstat
                    Heap             Gen0         Gen1         Gen2          LOH
                    Heap0          109048           24           24        34600
                    
                    Free space:                                                 Percentage
                    Heap0              24            0            0          152SOH:  0% LOH:  0%
                    
                    0:003> !dumpheap 0x0000009156561000
                             Address               MT     Size
                    0000009156561000 000000914483b920       24 Free
                    0000009156561018 000000914483b920       30 Free
                    0000009156561038 00007ffefe1b4918     8848     
                    00000091565632c8 000000914483b920       30 Free
                    00000091565632e8 00007ffefe1b4918     1056     
                    0000009156563708 000000914483b920       30 Free
                    0000009156563728 00007ffefe1b4918     8192     
                    0000009156565728 000000914483b920       30 Free
                    0000009156565748 00007ffefe1b4918    16352     
                    
                    Statistics:
                                  MT    Count    TotalSize Class Name
                    000000914483b920        5          144      Free
                    00007ffefe1b4918        4        34448 System.Object[]
                    Total 9 objects
                    
                    0:003> !dumpheap -type System.Double[]
                             Address               MT     Size
                    0000009146566a08 00007ffefe218450    80424     
                    
                    Statistics:
                                  MT    Count    TotalSize Class Name
                    00007ffefe218450        1        80424 System.Double[]
                    Total 1 objects
                    
                    0:003> !do 0000009146566a08
                    Name:        System.Double[]
                    MethodTable: 00007ffefe218450
                    EEClass:     00007ffefdc05138
                    Size:        80424(0x13a28) bytes
                    Array:       Rank 1, Number of elements 10050, Type Double
                    Fields:
                    None
                    

                      0
                      In 32-bit architrctures CLR’s execution engine attempts to place these arrays > 1000 doubles on the LOH because of the performance benefit of accessing aligned doubles. However there are no benefits of applying the same heuristics on 64-bit architectures because doubles are already aligned on an 8-byte boundary. As such we have disabled this heuristics for 64-bit architectures in .,NET 4.5

                      Thanks,

                      Abhishek Mondal

                      Program Manager

                      Common Language Runtime

                      http://blogs.msdn.com/b/dotnet/archive/2011/10/04/large-object-heap-improvements-in-net-4-5.aspx

                      и еще
                      http://stackoverflow.com/questions/11791038/large-object-heap-fragmentation-issues-with-arrays
                      https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=266330&wa=wsignin1.0

                      p.s.
                      у Вас похоже пришел апдейт.
                        0
                        Спасибо, не знал.
                        x32 .NET 4.5

                        1001
                        0:003> !eeheap -gc
                        Number of GC Heaps: 1
                        generation 0 starts at 0x02c01018
                        generation 1 starts at 0x02c0100c
                        generation 2 starts at 0x02c01000
                        ephemeral segment allocation context: none
                                 segment             begin         allocated  size
                        02c00000  02c01000  02c05ff4  0x4ff4(20468)
                        Large object heap starts at 0x03c01000
                                 segment             begin         allocated  size
                        03c00000  03c01000  03c07300  0x6300(25344)
                        Total Size:              Size: 0xb2f4 (45812) bytes.
                        ------------------------------
                        GC Heap Size:    Size: 0xb2f4 (45812) bytes.
                        
                        0:003> !heapstat
                        Heap             Gen0         Gen1         Gen2          LOH
                        Heap0           20444           12           12        25344
                        
                        Free space:                                                 Percentage
                        Heap0              12            0            0           96SOH:  0% LOH:  0%
                        
                        0:003> !dumpheap -type System.Double[]
                         Address       MT     Size
                        03c053a8 70bafe70     8020     
                        
                        Statistics:
                              MT    Count    TotalSize Class Name
                        70bafe70        1         8020 System.Double[]
                        Total 1 objects
                        
                        0:003> !do 03c053a8
                        Name:        System.Double[]
                        MethodTable: 70bafe70
                        EEClass:     70811530
                        Size:        8020(0x1f54) bytes
                        Array:       Rank 1, Number of elements 1001, Type Double
                        Fields:
                        None
                        
                        0:003> !dumpheap 0x03c01000
                         Address       MT     Size
                        03c01000 01230af8       10 Free
                        03c01010 01230af8       14 Free
                        03c01020 70b60cbc     4424     
                        03c02168 01230af8       14 Free
                        03c02178 70b60cbc      528     
                        03c02388 01230af8       14 Free
                        03c02398 70b60cbc     4096     
                        03c03398 01230af8       14 Free
                        03c033a8 70b60cbc     8176     
                        03c05398 01230af8       14 Free
                        03c053a8 70bafe70     8020     
                        
                        Statistics:
                              MT    Count    TotalSize Class Name
                        01230af8        6           80      Free
                        70bafe70        1         8020 System.Double[]
                        70b60cbc        4        17224 System.Object[]
                        Total 11 objects
                        

                          0
                          arrays > 1000 doubles

                          Если быть более точным, то >= 1000

                    Only users with full accounts can post comments. Log in, please.