C# — C# — Возможно ли(как) использовать «


Содержание

Типы приложений .NET

Язык C# можно использовать для создания консольных приложений — текстовых приложений, запускаемых в окне DOS. Скорее всего, такие приложения будут применяться для тестирования библиотек классов. Однако гораздо чаще язык C# придется использовать для создания приложений, имеющих доступ к множеству технологий, связанных с .NET. В данной статье представлен краткий обзор различных типов приложений, которые можно писать на C#.

Создание приложений ASP.NET

Изначально появление ASP.NET 1.0 фундаментально изменило модель веб-программирования. ASP.NET 4 является старшим выпуском продукта и построен на базе прежних достижений. В ASP.NET 4 реализованы основные революционные шаги, направленные на повышение продуктивности работы. Главная цель ASP.NET 4 — позволить строить мощные, безопасные, динамичные приложения с использованием минимально возможного объема кода.

Возможности ASP.NET

Первое, и, возможно, самое важное — это то, что страницы структурированы. То есть каждая страница — это, по сути, класс, унаследованный от класса .NET System.Web.UI.Page, который может переопределить набор методов, вызываемых во время жизненного цикла объекта Page. (Можете воспринимать эти события как специфичные для страниц родственники событий OnApplication_Start и OnSession_Start, которые поступали в файлы global.asax в классической платформе ASP.) Поскольку функциональность страниц можно выделить в обработчики событий определенного назначения, страницы ASP.NET гораздо легче понять.

Другая приятная особенность страниц ASP.NET заключается в том, что их можно создавать в Visual Sludio 2010 — той же среде, в которой программируется бизнес-логика и компоненты доступа к данным, используемые этими же страницами ASP.NET. Проект Visual Studio 2010, или решение (solution), содержит все файлы, ассоциированные с приложением. Более того, классические ASP-страницы можно отлаживать непосредственно в редакторе. В прежние времена, связанные с Visual InterDev, существовала неприятная необходимость специально конфигурировать InterDev и веб-сервер проекта для включения отладки.

Для большей ясности средство отделенного кода (code-behind) ASP.NET позволяет распространить структурированный подход еще дальше. ASP.NET позволяет изолировать серверную функциональность страницы в классе, скомпилировать этот класс вместе с другими страницами в библиотеку DLL и разместить эту DLL в каталоге, находящемся ниже части HTML. Директива @Page, указанная в начале страницы, ассоциирует этот файл с классом. Когда браузер запрашивает страницу, веб-сервер генерирует события в классе, находящемся в файле класса страницы.

Последнее, но не менее важное свойство ASP.NET, которое следует упомянуть — это увеличенная производительность. В то время как классические ASP-страницы интерпретировались при каждом обращении к странице, страницы ASP.NET после компиляции подвергаются кэшированию на веб-сервере. Это значит, что все последующие запросы страниц ASP.NET выполняются быстрее, чем первый.

ASP.NET также облегчает написание страниц, отображающих формы в браузере, которые можно применять в корпоративной сети. Традиционное мнение о приложениях, ориентированных на формы, заключается в том, что они предоставляют развитый пользовательский интерфейс, однако их сложнее сопровождать, поскольку они запускаются на многих клиентских машинах. По этой причине люди полагаются на приложения, ориентированные на формы, только тогда, когда необходим развитый интерфейс и имеется возможность обеспечить надежную поддержку пользователям.

Веб-формы

Чтобы еще более облегчить конструирование веб-страниц, в Visual Studio 2010 предлагается библиотека Web Forms (Веб-формы). Это средство позволяет графически создавать страницы ASP.NET — точно так же, как создаются окна в Visual Basic 6 и в C++Builder. Другими словами, потребуется перетащить необходимые элементы управления из панели инструментов на поверхность формы, затем слегка подкорректировать код формы и написать обработчики событий для элементов управления. При использовании языка C# для создания веб-формы, вы создаете класс C#, унаследованный от базового класса Page, и страницу ASP.NET, которая отображает этот класс в качестве своего отделенного кода. Разумеется, вы не обязаны применять для создания веб-формы именно C#; для этого можно воспользоваться также Visual Basic 2010 или любым другим языком, ориентированным на .NET.

В прошлом сложность веб-разработки удерживала некоторые команды разработчиков от попыток ее освоения. Для достижения успехов в разработке веб-приложений приходилось знать множество разных технологий, таких как VBScript, ASP, DHTML, JavaScript и т.д. Применение концепций Web Forms к веб-страницам существенно упростило построение веб-приложений.

Элементы управления веб-сервера

Элементы управления, применяемые в веб-формах, не являются элементами управления в том же смысле, в каком ими являются элементы ActiveX. Вместо этого они представляют собой XML-дескрипторы, принадлежащие пространству имен ASP.NET, которые веб-браузер динамически трансформирует в HTML-дескрипторы и сценарии клиентской стороны, когда осуществляется запрос этой страницы. Удивительно то, что веб-сервер может отображать одни и те же элементы управления серверной стороны различными способами, генерируя трансформацию, соответствующую конкретному веб-браузеру, который прислал запрос. Это означает, что теперь можно легко создавать совершенные пользовательские интерфейсы веб-страниц, не заботясь о том, как достичь того, чтобы страница работала в любом из доступных браузеров — об этом позаботится Web Forms.

Веб-службы XML

На сегодняшний день HTML-страницы составляют большую часть трафика World Wide Web. Однако благодаря XML, компьютеры получают независимый от устройства формат, который может использоваться для их взаимодействия в Интернете. В будущем для обмена информацией компьютеры смогут использовать XML вместо того, чтобы пересылать друг другу по выделенным линиям сообщения в специальном патентованном формате, подобном EDI (Electronic Data Interchange — электронный обмен данными). Веб-службы XML предназначены для веб-среды, ориентированной на службы, когда удаленные компьютеры предоставляют друг другу динамическую информацию, которая может быть проанализирована и переформатирована, прежде чем получить свое окончательное представление для пользователя. Веб-службы XML — это простой способ для компьютеров предоставлять информацию в форме XML другим компьютерам в Интернете.

В рамках технической терминологии веб-служба XML в .NET — это страница ASP.NET, которая возвращает по запросам клиентов XML-код вместо HTML-кода. Такие страницы опираются на DLL-библиотеки отделенного кода, которые содержат классы, унаследованные от WebService. Интегрированная среда разработки Visual Studio 2010 предлагает механизм, облегчающий создание веб-служб.

Предприятие или организация может отдать предпочтение веб-службам XML по двум основным причинам. Первая причина заключается в том, что они основаны на HTTP; вебслужбы могут использовать существующие сети на базе HTTP в качестве посредников для передачи информации. Другая причина связана с тем, что поскольку веб-службы XML применяют XML, формат передаваемых данных является самодокументированным, открытым и независящим от платформы.

Создание Windows-форм

Хотя C# и .NET предназначены, в частности, для веб-разработки, они по-прежнему поддерживают так называемые приложения «толстого» клиента — т.е. приложения, которые должны устанавливаться на машине конечного пользователя, где и выполняется большая часть обработки. Это обеспечивает библиотека Windows forms (Windows-формы).

Однако, если вы начинаете изучать платформу .NET, я бы не рекомендовал вам изучать Windows Forms, а сразу переходить к изучению новейшей технологии WPF.

Windows Presentation Foundation (WPF)

Одной из новейших технологий революционного характера является Windows Presentation Foundation (WPF). При построении приложений WPF опирается на использование XAML. Аббревиатура XAML означает . Этот новый способ создания приложений внутри среды Microsoft, впервые представленный в 2006 г., является частью версий .NET Framework 3.0, 3.5 и 4. Это значит, что для того, чтобы запустить любое приложение WPF, необходимо удостовериться, что на машине клиента установлена платформа .NET Framework 3.0, 3.5 или 4. Приложения WPF доступны для ОС Windows 7, Windows Vista, Windows XP, Windows Server 2003 и Windows Server 2008 (только эти операционные системы позволяют устанавливать .NET Framework 3.0, 3.5 или 4).

XAML — это объявление XML, используемое для создания форм, которые представляют все визуальные аспекты и поведение приложений WPF. Поскольку с приложением WPF можно работать программно, эта технология представляет собой шаг в направлении декларативного программирования, к которому движется сегодня вся отрасль. Декларативное программирование означает, что вместо создания объектов посредством написания кода на компилируемом языке, таком как C#, VB или Java, все объявляется с помощью XML-подобного программирования.

Службы Windows

(Windows Service, изначально называвшаяся NT Service) — это программа, предназначенная для запуска в фоновом режиме в среде Windows NT/2000/XP/2003/ Vista/7 (но не Windows 9х). Эти службы удобны, когда нужно организовать непрерывную работу программ, готовых реагировать на события, без необходимости явного их запуска по инициативе пользователя. Хорошим примером является служба World Wide Web Service на веб-серверах, которая прослушивает веб-запросы клиентов.

Разрабатывать службы на языке C# очень просто. В пространстве имен System.Service.Process доступны базовые классы среды .NET, которые позволяют решать большинство шаблонных задач, связанных с системными службами. Кроме того, среда Visual Studio .NET позволяет создавать проект C# Windows Service, в котором для построения службы Windows используется исходный код C#.

Windows Communication Foundation (WCF)

Памятуя об этом, в Microsoft действительно собрали все эти технологии вместе, и в версии .NET Framework 3.0, а также и в ее включении в .NET Framework 3.5 и 4, имеется единый способ перемещения данных — Windows Communication Foundation (WCF). Библиотека WCF обеспечивает возможностью первоначального построения службы с последующей доставкой этой службы различными путями (даже по разным протоколам) простым изменением конфигурационного файла. WCF — очень мощное новое средство соединения распределенных систем.

Windows Workflow Foundation (WF)

Служба Windows Workflow Foundation (WF) впервые была представлена в версии .NET Framework 3.0, но теперь подверглась основательной перестройке, в результате чего многие теперь найдут ее гораздо более полезной. Вы обнаружите, что в Visual Studio 2010 значительно улучшены средства работы с WF, и создавать собственные рабочие потоки стало гораздо удобней. Вы также найдете новые средства управления потоком, класс Flowchart, а также ряд новых действий, таких как DoWhile, ForEach и ParallelForEach.

Методы (Руководство по программированию на C#) Methods (C# Programming Guide)

Метод — это блок кода, содержащий ряд инструкций. A method is a code block that contains a series of statements. Программа инициирует выполнение инструкций, вызывая метод и указывая все аргументы, необходимые для этого метода. A program causes the statements to be executed by calling the method and specifying any required method arguments. В C# все инструкции выполняются в контексте метода. In C#, every executed instruction is performed in the context of a method. Метод Main является точкой входа для каждого приложения C# и вызывается общеязыковой средой выполнения (CLR) при запуске программы. The Main method is the entry point for every C# application and it’s called by the common language runtime (CLR) when the program is started.

В этой статье рассматриваются названные методы. This article discusses named methods. Сведения об анонимных функциях см. в разделе Анонимные функции. For information about anonymous functions, see Anonymous Functions.

Сигнатуры методов Method signatures

Методы объявляются в классе или в структуре путем указания уровня доступа, такого как public или private , необязательных модификаторов, таких как abstract или sealed , возвращаемого значения, имени метода и всех параметров этого метода. Methods are declared in a class or struct by specifying the access level such as public or private , optional modifiers such as abstract or sealed , the return value, the name of the method, and any method parameters. Все эти части вместе представляют собой сигнатуру метода. These parts together are the signature of the method.

Тип возврата метода не является частью сигнатуры метода в целях перегрузки метода. A return type of a method is not part of the signature of the method for the purposes of method overloading. Однако он является частью сигнатуры метода при определении совместимости между делегатом и методом, на который он указывает. However, it is part of the signature of the method when determining the compatibility between a delegate and the method that it points to.

Параметры метода заключаются в скобки и разделяются запятыми. Method parameters are enclosed in parentheses and are separated by commas. Пустые скобки указывают, что параметры методу не требуются. Empty parentheses indicate that the method requires no parameters. Этот класс содержит четыре метода: This class contains four methods:

Доступ к методу Method access

Вызов метода в объекте аналогичен доступу к полю. Calling a method on an object is like accessing a field. После имени объекта добавьте точку, имя метода и круглые скобки. After the object name, add a period, the name of the method, and parentheses. Аргументы перечисляются в этих скобках и разделяются запятыми. Arguments are listed within the parentheses, and are separated by commas. Таким образом, методы класса Motorcycle могут вызываться, как показано в следующем примере: The methods of the Motorcycle class can therefore be called as in the following example:

Параметры и аргументы метода Method parameters vs. arguments

Определение метода задает имена и типы всех необходимых параметров. The method definition specifies the names and types of any parameters that are required. Когда вызывающий код вызывает метод, он предоставляет конкретные значения, называемые аргументами, для каждого параметра. When calling code calls the method, it provides concrete values called arguments for each parameter. Аргументы должны быть совместимы с типом параметра, но имя аргумента (если есть), используемое в вызывающем коде, не обязательно должно совпадать с именем параметра, указанным в методе. The arguments must be compatible with the parameter type but the argument name (if any) used in the calling code doesn’t have to be the same as the parameter named defined in the method. Например: For example:

Передача по ссылке и передача по значению Passing by reference vs. passing by value

По умолчанию при передаче в метод типа значения вместо самого объекта передается его копия. By default, when a value type is passed to a method, a copy is passed instead of the object itself. Поэтому изменения в аргументе не оказывают влияния на исходную копию в вызывающем методе. Therefore, changes to the argument have no effect on the original copy in the calling method. Вы можете передавать тип значения по ссылке с помощью ключевого слова ref. You can pass a value-type by reference by using the ref keyword. Дополнительные сведения см. в разделе Передача параметров типа значения. For more information, see Passing Value-Type Parameters. Список встроенных типов значений см. в разделе Таблица типов значений. For a list of built-in value types, see Value Types Table.

При передаче в метод объекта ссылочного типа передается ссылка на этот объект. When an object of a reference type is passed to a method, a reference to the object is passed. То есть метод получает не сам объект, а аргумент, который указывает расположение объекта. That is, the method receives not the object itself but an argument that indicates the location of the object. При изменении члена объекта с помощью этой ссылки это изменение отражается в аргументе в вызывающем методе, даже если объект передается по значению. If you change a member of the object by using this reference, the change is reflected in the argument in the calling method, even if you pass the object by value.

Ссылочный тип создается с помощью ключевого слова class , как показано в следующем примере. You create a reference type by using the class keyword, as the following example shows:

Теперь, если передать объект, основанный на этом типе, в метод, то будет передана ссылка на объект. Now, if you pass an object that is based on this type to a method, a reference to the object is passed. В следующем примере объект типа SampleRefType передается в метод ModifyObject : The following example passes an object of type SampleRefType to method ModifyObject :

В этом примере, в сущности, делается то же, что и в предыдущем примере, — аргумент по значению передается в метод. The example does essentially the same thing as the previous example in that it passes an argument by value to a method. Но поскольку здесь используется ссылочный тип, результат будет другим. But, because a reference type is used, the result is different. В данном случае в методе ModifyObject изменено поле value параметра obj , а также изменено поле value аргумента, rt в методе TestRefType . The modification that is made in ModifyObject to the value field of the parameter, obj , also changes the value field of the argument, rt , in the TestRefType method. В качестве выходных данных метод TestRefType отображает 33. The TestRefType method displays 33 as the output.

Дополнительные сведения о передаче ссылочных типов по ссылке и по значению см. в разделах Передача параметров ссылочного типа и Ссылочные типы. For more information about how to pass reference types by reference and by value, see Passing Reference-Type Parameters and Reference Types.

Возвращаемые значения Return values

Методы могут возвращать значение вызывающему объекту. Methods can return a value to the caller. Если тип возврата, указываемый перед именем метода, не void , этот метод может возвращать значение с помощью ключевого слова return . If the return type, the type listed before the method name, is not void , the method can return the value by using the return keyword. Инструкция с ключевым словом return , за которым следует значение, соответствующее типу возврата, будет возвращать это значение объекту, вызвавшему метод. A statement with the return keyword followed by a value that matches the return type will return that value to the method caller.

Значение может возвращаться вызывающему объекту по значению или, начиная с C# версии 7.0, по ссылке. The value can be returned to the caller by value or, starting with C# 7.0, by reference. Значения возвращаются вызывающему объекту по ссылке, если ключевое слово ref используется в сигнатуре метода и указывается после каждого ключевого слова return . Values are returned to the caller by reference if the ref keyword is used in the method signature and it follows each return keyword. Например, следующая сигнатура метода и оператор return указывают, что метод возвращает имена переменных estDistance вызывающему объекту по ссылке. For example, the following method signature and return statement indicate that the method returns a variable names estDistance by reference to the caller.

Ключевое слове return также останавливает выполнение метода. The return keyword also stops the execution of the method. Если тип возврата — void , инструкцию return без значения по-прежнему можно использовать для завершения выполнения метода. If the return type is void , a return statement without a value is still useful to stop the execution of the method. Без ключевого слова return этот метод будет останавливать выполнение при достижении конца блока кода. Without the return keyword, the method will stop executing when it reaches the end of the code block. Методы с типом возврата, отличным от void, должны использовать ключевое слово return для возврата значения. Methods with a non-void return type are required to use the return keyword to return a value. Например, в следующих двух методах ключевое слово return используется для возврата целочисленных значений. For example, these two methods use the return keyword to return integers:

Чтобы использовать значение, возвращаемое из метода, вызывающий метод может применять сам вызов метода везде, где будет достаточно значения того же типа. To use a value returned from a method, the calling method can use the method call itself anywhere a value of the same type would be sufficient. Можно также назначить возвращаемое значение переменной. You can also assign the return value to a variable. Например, следующие два примера кода достигают одной и той же цели. For example, the following two code examples accomplish the same goal:

Использование локальной переменной, в данном случае result , для сохранения значения является необязательным. Using a local variable, in this case, result , to store a value is optional. Это может улучшить читаемость кода или может оказаться необходимым, если нужно сохранить исходное значение аргумента для всей области метода. It may help the readability of the code, or it may be necessary if you need to store the original value of the argument for the entire scope of the method.

Чтобы использовать значение, возвращаемое по ссылке из метода, необходимо объявить локальную ссылочную переменную, если планируется изменение значения. To use a value returned by reference from a method, you must declare a ref local variable if you intend to modify its value. Например, если метод Planet.GetEstimatedDistance возвращает значение Double по ссылке, можно определить его как локальную ссылочную переменную с использованием кода следующего вида: For example, if the Planet.GetEstimatedDistance method returns a Double value by reference, you can define it as a ref local variable with code like the following:

Возвращать многомерный массив из метода M , который изменяет содержимое массива, необязательно, если вызывающая функция передает массив в M . Returning a multi-dimensional array from a method, M , that modifies the array’s contents is not necessary if the calling function passed the array into M . В целях оптимизации можно возвращать полученный массив из M или функциональный поток значений, однако это необязательно. Это связано с тем, что C# передает все ссылочные типы по значению, а значение ссылки на массив представляет собой указатель на массив. You may return the resulting array from M for good style or functional flow of values, but it is not necessary because C# passes all reference types by value, and the value of an array reference is the pointer to the array. В методе M любые изменения содержимого массива отслеживаются любым кодом, имеющим ссылку на массив, как показано в приведенном ниже примере: In the method M , any changes to the array’s contents are observable by any code that has a reference to the array, as shown in the following example:

Для получения дополнительной информации см. return. For more information, see return.

Асинхронные методы Async methods

С помощью функции async можно вызывать асинхронные методы, не прибегая к использованию явных обратных вызовов или ручному разделению кода между несколькими методами или лямбда-выражениями. By using the async feature, you can invoke asynchronous methods without using explicit callbacks or manually splitting your code across multiple methods or lambda expressions.

Если пометить метод с помощью модификатора async , можно использовать в этом методе инструкцию await . If you mark a method with the async modifier, you can use the await operator in the method. Когда управление достигает выражения await в асинхронном методе, управление возвращается вызывающему объекту и выполнение метода приостанавливается до завершения выполнения ожидающей задачи. When control reaches an await expression in the async method, control returns to the caller, and progress in the method is suspended until the awaited task completes. После завершения задачи можно возобновить выполнение в методе. When the task is complete, execution can resume in the method.

Асинхронный метод возвращается в вызывающий объект, когда он встречает первый ожидаемый объект, выполнение которого еще не завершено, или когда выполнение асинхронного метода доходит до конца — в зависимости от того, что происходит раньше. An async method returns to the caller when either it encounters the first awaited object that’s not yet complete or it gets to the end of the async method, whichever occurs first.

Асинхронный метод может иметь тип возврата Task , Taskили void. An async method can have a return type of Task , Task, or void. Тип возврата void в основном используется для определения обработчиков событий, где требуется тип возврата void. The void return type is used primarily to define event handlers, where a void return type is required. Асинхронный метод, который возвращает тип void, не может быть ожидающим. Вызывающий объект метода, возвращающего значение типа void, не может перехватывать исключения, которые выдает этот метод. An async method that returns void can’t be awaited, and the caller of a void-returning method can’t catch exceptions that the method throws.

В следующем примере метод startButton_Click служит примером асинхронного метода с типом возврата void. The startButton_Click method is an example of an async method that has a return type of void. Поскольку DoSomethingAsync является асинхронным методом, задача для вызова DoSomethingAsync должна быть ожидаемой, как показывает следующая инструкция: await DoSomethingAsync(); . Because DoSomethingAsync is an async method, the task for the call to DoSomethingAsync must be awaited, as the following statement shows: await DoSomethingAsync(); . Метод startButton_Click должен быть определен с модификатором async , так как этот метод имеет выражение await . The startButton_Click method must be defined with the async modifier because the method has an await expression.

Асинхронный метод не может объявить все параметры ref или out , но может вызывать методы, которые имеют такие параметры. An async method can’t declare any ref or out parameters, but it can call methods that have such parameters.

Определения текста выражений Expression body definitions

Часто используются определения методов, которые просто немедленно возвращаются с результатом выражения или которые имеют единственную инструкцию в тексте метода. It is common to have method definitions that simply return immediately with the result of an expression, or that have a single statement as the body of the method. Для определения таких методов существует сокращенный синтаксис с использованием => : There is a syntax shortcut for defining such methods using => :

Если метод возвращает void или является асинхронным методом, то текст метода должен быть выражением инструкции (так же, как при использовании лямбда-выражений). If the method returns void or is an async method, then the body of the method must be a statement expression (same as with lambdas). Свойства и индексаторы должны быть только для чтения, и вы не должны использовать ключевое слово get метода доступа. For properties and indexers, they must be read only, and you don’t use the get accessor keyword.

Iterators Iterators

Итератор выполняет настраиваемую итерацию по коллекции, например по списку или массиву. An iterator performs a custom iteration over a collection, such as a list or an array. Итератор использует инструкцию yield return для возврата всех элементов по одному. An iterator uses the yield return statement to return each element one at a time. Когда достигается инструкция yield return , текущее расположение в коде запоминается. When a yield return statement is reached, the current location in code is remembered. При следующем вызове итератора выполнение возобновляется с этого места. Execution is restarted from that location when the iterator is called the next time.

Итератор вызывается из клиентского кода с помощью инструкции foreach . You call an iterator from client code by using a foreach statement.

Тип возврата итератора может быть IEnumerable, IEnumerable , IEnumeratorили IEnumerator . The return type of an iterator can be IEnumerable, IEnumerable , IEnumerator, or IEnumerator .

Дополнительные сведения см. в разделе Итераторы. For more information, see Iterators.

Спецификация языка C# C# language specification

Дополнительные сведения см. в спецификации языка C#. For more information, see the C# Language Specification. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#. The language specification is the definitive source for C# syntax and usage.

Обучение C# (c sharp)

Данный видеокурс представлен для новичков в языке программирования C#. Если Вы решили, что готовы начать обучение программированию, то стартуйте вместе с нами! Курс программирования C# Стартовый поможет Вам начать Ваше знакомство с языком программирования C#. Узнайте основы современного программирования на Visual C # 2012 и приступите к созданию Вашего первого приложения на языке C#.

Этот видео урок помогает разобраться с двоичной и шестнадцатеричной системами счисления. Также Вы узнаете, какие простые типы данных есть в языке программирования C# и сможете правильно определять, какой тип данных следует использовать в конкретной ситуации.

Этот видео урок помогает разобраться с типами данных. Вы узнаете, какие простые типы данных есть в языке программирования C# и сможете правильно определять, какой тип данных следует использовать в конкретной ситуации.

На этом видео уроке Вы изучите условные операторы, позволяющие разрабатывать алгоритмы, в которых от выбора пользователя или от других условий будет зависеть результат. В этом уроке будут рассмотрены такие условные конструкции языка программирования C# — if/else, switch, тернарный оператор.

Данный видео урок рассматривает понятия логических и побитовые операции. На видеокурсе объясняется применение и использование операций конъюнкции, дизъюнкции, исключающего «или», логических операторов, операторов сдвига в языке программирования C#.

Видео урок полностью посвящен циклическим конструкциям — то, без чего не обойдется ни одна программа. В программировании часто встречаются действия, которые необходимо повторить несколько раз. Для решения подобных задач, в которых требуются неоднократное повторение операций, Вам необходимо разобраться с конструкциями, которые будут изучены в этом видео уроке.

Чтобы научится программировать на языке C#, мало изучить условные и циклические конструкции. Необходимо использовать еще и другие возможности языка C#. В этом видео уроке Вы рассмотрите важный элемент любого приложения — метод.

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

Главная задача для бoльшинствa приложeний cводится к aнализу дaнных и вычиcлений, производимых нaд бoльшими объeмами дaнных. Для тoго, чтoбы в Вaших прилoжениях пoявилась возможноcть хрaнить многo однoтипных знaчений, Вaм нужно cоздавать маcсив. Видeо урок обучит Вaс нe тoлько кaк сoздать мaссивы, но и произвoдить oсновные опeрации нaд ними. Вы узнаeте, какиe нужно использовaть циклы при рaботе с мaссивами и изучите такие типы массивов как одномерные, многомерные, зубчатые.

Практикум курса C# Стартовый на примерах из GTA 5

Роман Самчук подготовил новый необычный курс для желающих выучить C#. Чтобы знания усваивались необходима практика, а что может быть интереснее и практичнее, чем мод для GTA V? Именно на его примере мы разберем все основные синтаксические конструкции языка и их особенности употребления. Видео будет крайне полезно новичкам в программировании.

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

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

Встречайте новый урок от Романа Самчука. На предыдущих занятиях мы рассмотрели с вами, как создавать статические вещи в игре. В этот раз, мы добавим в игру невероятно удобного персонажа — киллера. Научим его ходить, выбирать цель и делать за нас работу — убивать персонажей в игре.

В этом уроке будем работать с native function, которые есть в игре. Будет показано, как их можно вызывать, как передавать им различные параметры. Также, создадим машину с четырьмя врагами и попробуем из перестрелять.

Видео курс C# Базовый.

На первом уроке видео курса C# Essential будет рассмотрено: Концепции объектно-ориентированного программирования (ООП). Классы в языке C#. Основы работы с конструктором. Назначение и использование свойств (get, set). Главных парадигмы ООП.

В видео уроке «Классы и объекты. Диаграммы классов» будет продолжена тема урока «Введение в OOП. Классы и объекты», а также будет раскрыта тема возможности языка программирования C# разделять определение класcа между двумя и/или более файлами, именуемая частичными или partial классами. После ознакомления с частичными классами в С#, будут рассмотрены диаграммы классов, связи отношений между классами такие как ассоциация, агрегация, композиция, реализация, самоассоциация зависимости и другие.

В ходе видео урока Вам будет представлена информация о двух основных парадигмах ООП — полиморфизме и наследовании, также Вы познакомитесь с модификаторами доступа и виртуальными членами.

В видео уроке будет продемонстрированы практические примеры создания и использования абстрактных классов и интерфейсов. Абстракция позволяет программисту рассматривать объект, не разбирая сумму сложных частей, из которых состоит данный объект. В ходе видео урока Вы получите необходимые знания, которые помогут Вам разобраться с понятием абстракции в С#.

В видео уроке рассмотрены примеры создания и практического применения массивов и индексаторов. Тренер объясняет Вам принципы создания и практического применения индексаторов и способы их переопределения. Программисты с опытом знакомы с процессом обращения к индивидуальным элементам, которые содержат стандартные массивы.

В видео уроке будут рассмотрены статические классы, принципы создания и практического применения статических членов. Также, в ходе видео урока будет объяснена работа и использование расширяющих методов.Во второй части видео урока тренер рассмотрит понятие вложенных классов и шаблон проектирования «Одиночка» (Singleton).

Цукерберг рекомендует:  5 известных женщин мира IT

Описание: В видео уроке будет представлена полная информация о структурах, рассмотрены отличия между классами и структурами, а также рассказаны практические советы по их применению. Структуры — фундаментальные типы данных в языке программирования C#.

В данном видео уроке будут рассмотрены такие понятия как упаковка (boxing) и распаковка (unboxing), структурный тип DateTime, а также работа с перечислениями(enum). В ходе занятия тренер ознакомит студентов с практическими примерами, которые позволят с легкостью использовать и применять полученные на уроке знания.

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

В языке программирования C# существует два механизма для создания кода, который будет повторно использован через различные типы — уже рассмотренное ранее наследование и обобщения. Обобщения, в отличии от наследования, выражают повторное использование кода через использование универсальных шаблонов(generics), в которых применяются различные типы данных на этапе выполнения.

Описание: В видео уроке «Ограничения универсальных шаблонов» Вас ждет продолжение знакомства с универсальными шаблонами в C#. Вы узнаете, каким образом можно использовать ограничения для обобщенных типов данных. В ходе видео урока тренер остановит Ваше внимание на работе с Nullable типами, а также операциях поглощения, показав примеры практического их использования.

Весь видео урок будет всецело посвящен работе с событиями в C#. В деталях будет рассмотрено, каким образом создавать «издателей» и «подписчиков», а также обращаться к созданным событиям и вызывать их. Тренер уделит отдельное внимание делегату EventHandler и базовому классу EventArgs, а также работе с ними

В процессе просмотра видео урока Вы получите основные сведения, которые потребуются Вам для работы с многопоточностью в языке программирования C#. Многопоточность — важное средство многозадачного программирования среды .NET. Видео урок даст Вам основное понимание многопоточности в языке программирования С#. Также в ходе урока тренер расскажет Вам об использовании делегатов ThreadStart и ParameterizedThreadStart и объяснит работу с критическими секциями, как средствами синхронизации доступа потоков к различным разделяемым ресурсам.

В видео уроке будут объяснены коллекции, их назначение и примеры их практического применения. Также Вы детально изучите базовые интерфейсы IEnumerable, IEnumerator. Также в ходе видео урока тренер рассмотрит с Вами примеры создания и использования пользовательских коллекций, продемонстрирует наглядные примеры по работе оператора yield.

В этом видео уроке Вы узнаете какие системные исключения существуют в языке C# и как правильно обрабатывать исключительные ситуации с помощью конструкции try — catch — finally. Также вы научитесь создавать свои объекты исключения. При выполнение приложения может сложится ситуация, когда корректное выполнение приложения невозможно. Например, приложение читает файл на диске, которого нет.


В данном видео уроке тренером будет рассмотрен базовый класс object его применение и использование, а так же техника перегрузки операторов. В процессе объяснения будет затронута техника клонирования, а также будет рассмотрено назначение шаблона проектирования «Прототип» (Prototype) и интерфейса ICloneable.

Данный видео урок будет очень важен для понимания всех современных технологий. В ходе видео урока тренер познакомит Вас с основами LINQ, а также с анонимными и динамическими типами, которые активно используются при построении запросов. Язык LINQ — это набор функций, который позволяет значительно расширить возможности синтаксиса языка программирования C#.

В этом видеоуроке Вы узнаете, что такое пространства имен и как правильно организовывать проект используя пространства имен. Также Вы узнаете, как создавать библиотеки (DLL) в языке C#. Тренер рассмотрит тип проекта Class Library и на простом примере объяснить для чего используются библиотеки.

dotnet_am

C# и платформа .NET в вопросах и ответах

.NET технологии в Армении

Если вы только начинаете изучать .NET, то вас, несомненно, интересует вопрос — на что она способна? Какие возможности вы получите изучив ее? Здесь я коротко расскажу, что можно сделать используя .NET. По секрету сообщу, что многие разработчики .NET тоже не в курсе некоторых ее возможностей, так что и я могу чего-то не знать. Расскажу то, что мне известно, а вы дополните.
Microsoft .NET Framework — платформа универсальная, и может решать множество задач и их число растет с каждой новой версией. Microsoft вкладывает огромные усилия в развитие .NET, справедливо считая его своей флагманской технологией.

Программирование для Windows.
По этой части .NET уже несколько лет стала технологией номер один и реальных конкурентов по скорости и удобству разработки у нее нет. Используя .NET можно писать WinForm (GUI) приложения, консольные приложения, dll файлы, сервисы Windows, графические приложения нового типа (WPF) и многое другое. В настоящее время имеются некоторые непокрытые области, например, написание драйверов устройств — одна из тех немногих областей где C/C++ упорно не сдают свои позиции. По скорости работы .NET практически не уступает коду написанному на C++, а в следующей версии (4.0) может и превзойти, благодаря технологии Parallel Extensions которая позволит с легкостью, без написания сложного многопоточного кода, использовать преимущества многоядерных процессоров .

Программирование для Web.
Технология, позволяющая разрабатывать Web-решения на .NET, называется ASP.NET. ASP.NET заточена для создания Web-приложений с богатой функциональностью. В отличии от Windows-приложений, здесь у нее много достойных и сильных конкурентов: PHP, Python, Ruby on Rails, Perl. Есть интересные решения, позволяющие сильно облегчить и ускорить разработку, например ASP.NET MVC Framework или Web Client Software Factory. Очень интересная технология Silverlight. Используя ее можно делать интерактивные, богатые графикой Web-приложения. Фактически Silverlight / C# , конкурент Adobe Flash / ActionScript. По моим субъективным ощущениям у Silverlight больше возможностей по сравнению с Flash.

Разработка распределенных корпоративный приложений.
И здесь у .NET тоже все путем. Web-сервисы, службы уровня предприятия, Remoting — все это делается легко и оптимально. Технология доступа к данным (ADO.NET), которая так важна для корпоративных решений, после версии Microsoft .NET Framework 2.0 стала почти идеальной. Единственный достойный конкурент для .NET в этой области — Java/J2EE.

Кросс-платформенное программирование.
Самой известной кросс-платформенной реализацией .NET является Mono. Это open-source проект спонсируемый фирмой Novell. Приложения написанные под Mono работают в Linux, FreeBSD , MacOS и Windows. В настоящее время Mono портируется под мобильные платформы Android и iPhone. К большому сожалению Microsoft не проявляет интереса к переносу .NET на другие платформы. Когда .NET только появлялась, они на всех углах кричали о том, что кроссплатформенность — ее второе имя. А потом заперлись в пределах Windows. Благодаря открытым спецификациям .NET open-source сообщества получили возможность реализовывать .NET технологии на других платформах, чем успешно и занимаются. Недавно в интернете прошли жаркие споры о том, стоит ли разрабатывать open-source используя Mono и включать ее в Linux дистрибутивы по умолчанию. Против этой идеи высказался сам папа open-source — Ричард Столлман, аргументировав это тем, что Microsoft в любое время может заявить свои права на технологию, поэтому open-source программы должны разрабатывается с использованием open-source технологий. Спустя некоторое время Microsoft заявила, что .NET открытая платформа с открытыми спецификации и свои права на сторонние ее реализации он не будет предъявлять.

Разработка для мобильных устройств
Версия .NET Framework для мобильных устройств работающих под Windows Mobile называется. .NET Compact Framework. Многие возможности обычной framework в ней отсутствуют за ненадобностью, вместо этого добавлены специальные наборы библиотек для мобильных устройств. Программы для мобильных устройств пишутся в обычной Visual Studio 2005/2008 после компиляции запускаются на эмуляторе, который так же входит в Visual Studio.
Для других мобильных платформ (Android, iPhone) делаются первые шаги и делает их, к сожалению, не Microsoft, а open-source сообщество Mono, о котором написано в предыдущем абзаце.

Программирование микроконтроллеров
Очень актуальная задача. Микроконтроллеров в десятки раз больше чем обычных процессоров. Они повсюду: в сотовых и обычных телефонах, телевизорах, мониторах, микроволновках, кондиционерах , стиральных машинах, холодильниках, MP3-плеерах, автомобилях, фотоаппаратах. И всех их надо запрограммировать. Представили какой объем и охват? Долгое время программирование микроконтроллеров было прерогативой C и Assembler-а, позже появилась возможность писать на некоторых языках высокого уровня. C# в их числе. Знакомтесь — .NET Micro Framework установив которую можете программировать 32-разрядные микроконтроллеры серии ARM7, ARM9 и Blackfin прямо в Visual Studio 2008. А здесь находится официальная страница Micro Framework .

Создание игр.
На заре .NET много говорилось о том, что он слишком медлителен и неповоротлив для написания тяжелых графических приложений. Оказалось, что это не так. В настоящее время .NET дружит с DirectX, а главное — появилась технология
Microsoft XNA — позволяет разрабатывать игры с использованием интенсивной 3D графики не только для Windows, но и для игровой консоли Xbox 360.

Программирование на системном уровне
Я уже писал, что на C# можно программировать микроконтроллеры. Если сильно захотеть, то можно даже левым способом сделать вставку машинного кода в C# (когда-нибудь расскажу об этом).
А можно ли на нем написать целую операционную систему? Оказывается — можно. Когда я впервые об этом прочитал — у меня глаза на лоб полезли. Операционная система на управляемом коде! И все-так, хотя и экспериментальная, она есть. Это Microsoft Singularity.

Вот, пожалуй и все. Если есть что спросить, добавить или исправить — пишите в комменты.

C# — C# — Возможно ли(как) использовать «

Контекст синхронизации (SynchronizationContext) — позволяет возобновить выполнение метода в конкретном потоке.
Текущий контекст SynchronizationContext – это свойство текущего потока. Идея в том, что всякий раз, как код исполняется в каком-то специальном потоке, мы
можем получить текущий контекст синхронизации и сохранить его.
Впоследствии этот контекст можно использовать для того, чтобы продолжить исполнение кода в том потоке, в котором оно было начато.

В классе SynchronizationContext есть важный метод Post , который гарантирует, что переданный делегат будет исполняться в правильном контексте.

Важно! В момент приостановки метода при встрече оператора await текущий контекст SynchronizationContext сохраняется.
Далее, когда метод возобновляется, компилятор вставляет вызов Post , чтобы исполнение возобновилось в запомненном контексте.

Код называется асинхронным , если он запускает какую-то длительную операцию, но не дожидается ее завершения. Противоположностью является блокирующий код, который ничего не делает, пока операция не завершится.

К числу таких длительных операций можно отнести:
• сетевые запросы;
• доступ к диску;
• продолжительные задержки.

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

Всякий раз, запуская новый поток или пользуясь классом ThreadPool, вы пишете асинхронную программу , потому что текущий поток может продолжать заниматься другими вещами.

Асинхронный код освобождает поток , из которого был запущен. И это очень хорошо по многим причинам.

Часто существует лишь один поток, способный выполнить определенную задачу (например, поток пользовательского интерфейса) и, если не освободить его быстро, то приложение перестанет реагировать на действия пользователя.

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

В версии C# 5.0 Microsoft добавила механизм, предстающий в виде двух новых ключевых слов: async и await .

Механизм async встроен в компилятор и без поддержки с его стороны не мог бы быть реализован в библиотеке. Компилятор преобразовывает исходный код, то есть действует примерно по тому же принципу, что в случае лямбда-выражений и итераторов в предыдущих версиях C#.

У ASP.NET-приложений на веб-сервере нет ограничения на единственный поток , как в случае программ с пользовательским интерфейсом. И тем не менее асинхронное выполнение может оказаться весьма полезным, так как для таких приложений характерны длительные операции, особенно запросы к базе данных.

В зависимости от версии IIS может быть ограничено либо общее число потоков, обслуживающих веб-запросы, либо общее число одновременно обрабатываемых запросов. Если большая часть времени обработки запроса уходит на обращение к базе данных, то увеличение числа одновременно обрабатываемых запросов может повысить пропускную способность сервера

Помните: основная отличительная особенность асинхронного кода состоит в том, что поток, начавший длительную операцию, освобождается для других дел .
В случае ASP.NET этот поток берется из пула потоков, поэтому после запуска длительной операции он сразу же возвращается в пул и может затем использоваться для обработки других запросов. Таким образом, для обработки одного и того же количества запросов требуется меньше потоков.

Библиотека Task Parallel Library была включена в версию .NET Framework 4.0. Важнейшим в ней является класс Task , представляющий выполняемую операцию. Его универсальный вариант, Task , играет роль обещания вернуть значение (типа T), когда в будущем, по завершении операции, оно станет доступно.

Как мы увидим ниже, механизм async в C# 5.0 активно пользуется классом Task. Но и без async классом Task, а особенно его вариантом Task , можно воспользоваться при написании асинхронных программ. Для этого нужно запустить операцию, которая возвращает Task , а затем вызвать метод ContinueWith для регистрации обратного вызова.
Это называется асинхронность вручную потому что используем метод ContinueWith .

Заметка на будующее:
Класс Task умеет в частности обрабатывать исключения и работать с контекстами синхронизации SynchronizationContext .
Мы увидим, что это полезно, когда требуется выполнить обратный вызов в конкретном потоке (например, в потоке пользовательского интерфейса).

1-ый шаг это пометка метода ключевым словом async .
Оно включается в сигнатуру метода точно так же, как, например, слово static.

2-ой шаг мы должны дождаться завершения скачивания, воспользовавшись ключевым словом await .
С точки зрения синтаксиса C#, await – это унарный оператор, такой же как оператор ! или оператор приведения типа (type).
Он располагается слева от выражения и означает, что нужно дождаться завершения асинхронного выполнения этого выражения.

На заметку!
Метод, помеченный ключевым словом async, автоматически не становится асинхронным .
Async-методы лишь упрощают использование других асинхронных методов. Они начинают исполняться синхронно, и так происходит до тех пор, пока не встретится вызов асинхронного метода внутри оператора await .
В этот момент сам вызывающий метод становится асинхронным.
Если же оператор await не встретится, то метод так и будет выполняться синхронно до своего завершения.

Я говорил, что класс Task представляет выполняемую операцию, а его подкласс Task – операцию, которая в будущем вернет значение типа T. Можно считать, что Task – это обещание вернуть значение типа T по завершении длительной операции.
Оба класса Task и Task могут представлять асинхронные операции, и оба умеют вызывать ваш код по завершении операции. Чтобы воспользоваться этой возможностью вручную, необходимо вызвать метод ContinueWith , передав ему код, который должен быть выполнен, когда длительная операция завершится. Именно так и поступает оператор await , чтобы выполнить оставшуюся часть async-метода .

Если применить await к объекту типа Task , то мы получим выражение await , которое само имеет тип T .
Это означает, что результат оператора await можно присвоить переменной, которая используется далее в методе, что мы и видели в примерах.
Однако если await применяется к объекту неуниверсального класса Task , то получается предложение await , которое ничему нельзя присвоить (как и результат метода типа vo >Task не обещает вернуть значение в качестве результата, а представляет лишь саму операцию.

Метод, помеченный ключевым словом async , может возвращать значения трех типов:
• void
• Task
• Task , где T – некоторый тип.

Никакие другие типы не допускаются, потому что в общем случае
исполнение асинхронного метода не завершается в момент возврата управления. Как правило, асинхронный метод ждет завершения длительной операции, то есть управление-то он возвращает сразу, но результат в этот момент еще не получен и, стало быть, недоступен вызывающей программе.

Я провожу различие между типом возвращаемого методом значения (например, Task ) и типом результата, который нужен вызывающей программе (в данном случае string).
В обычных, не асинхронных, методах тип возвращаемого значения совпадает с типом результата, тогда как в асинхронных методах они различны – и это очень существенно.

Использовать тип void следует, когда вы уверены, что вызывающей программе безразлично, когда завершится операция и завершится ли она успешно.

Async-методы , возвращающие тип Task , позволяют вызывающей программе ждать завершения асинхронной операции и распространяют исключения, имевшие место при ее выполнении.

Если значение результата несущественно, то метод типа async Task предпочтительнее метода типа async void , потому что вызывающая программа получает возможность узнать о завершении операции, что упрощает упорядочение задач и обработку исключений.

Ключевое слово async указывается в объявлении метода, как public или static.
Однако спецификатор async не считается частью сигнатуры метода, когда речь заходит о переопределении виртуальных методов, реализации интерфейса или вызове.
То есть в отношении переопределения методов и реализации интерфейсов, слово async полностью игнорируется.

В объявлениях методов интерфейса слово async запрещено просто потому, что в этом нет необходимости. Если в интерфейсе объявлен метод, возвращающий тип Task , то в реализующем интерфейс классе этот метод может быть помечен словом async, а может быть и не помечен – на усмотрение программиста.

Когда поток исполнения программы доходит до оператора await, должны произойти две вещи:

1) Текущий поток должен быть освобожден, чтобы поведение программы было асинхронным. С привычной, синхронной, точки зрения это означает, что метод должен вернуть управление.

2) Когда задача Task , погруженная в оператор await, завершится, ваш метод должен продолжить выполнение с того места, где перед этим вернул управление, как будто этого возврата никогда не было.

Чтобы добиться такого поведения, метод должен приостановить выполнение, дойдя до await , и возобновить его впоследствии.

Чтобы стало яснее, сколько работы должен выполнить компилятор C#, встретив в программе оператор await , я перечислю, какие именно аспекты состояния метода необходимо сохранить.

Во-первых, запоминаются все локальные переменные метода, в том числе:
• параметры метода
• все переменные, определенные в текущей области видимости
• все прочие переменные, например счетчики циклов
• переменную this, если метод не статический

В результате после возобновления метода окажутся доступны все переменные-члены класса.
Всё это сохраняется в виде объекта в куче .NET, обслуживаемой сборщиком мусора. Таким образом, встретив await , компилятор выделяет память для объекта, то есть расходует ресурсы, но в большинстве случае это не приводит к потере производительности.
C# также запоминает место, где встретился оператор await .

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

ExecutionContext — это родительский контекст, включающий все остальные контексты. Он не имеет собственного поведения, а служит только для запоминания и передачи контекста и используется такими компонентами .NET, как класс Task.

Оператор await можно использовать почти в любом месте метода, помеченного ключевым словом async .

Оператор await может встречаться внутри блока try , но не внутри блоков catch или finally .

В блоке catch часто, а в блоке finally всегда, исключение еще находится в фазе раскрутки стека и позже может быть возбуждено в блоке повторно.

Если использовать в этой точке await, то стек окажется другим, и определить в этой ситуации поведение повторного возбуждения исключения было бы очень сложно.
Напомню, что await всегда можно поставить не внутри блока catch, а после него, для чего следует либо воспользоваться предложением return, либо завести булевскую переменную, в которой запомнить, возбуждала ли исходная операция исключение. Например, вместо такого некорректного в C# кода:

можно было бы написать:

bool failed = false;

try
<
page = await webClient.DownloadStringTaskAsync( «http://aaa.com» );
>
catch (WebException)
<
failed = true;
>

if (failed)
<
page = await webClient.DownloadStringTaskAsync( «http://ooo.com» );
>

По завершении операции в объекте Task сохраняется информация о том, завершилась ли она успешно или с ошибкой. Получить к ней доступ проще всего с помощью свойства IsFaulted, которое равно true, если во время выполнения операции произошло исключение.
Оператор await знает об этом и повторно возбуждает исключение, хранящееся в Task.
У читателя, знакомого с системой исключений в .NET, может возникнуть вопрос, корректно ли сохраняется первоначальная трассировка стека исключения при его повторном возбуждении.

Раньше это было невозможно; каждое исключение могло быть возбуждено только один раз. Однако в .NET 4.5 это ограничение снято благодаря новому классу ExceptionDispatchInfo, который взаимодействует с классом Exception с целью запоминания трассировки стека и воспроизведения ее при повторном возбуждении.

Async-методы также знают об исключениях. Любое исключение, возбужденное, но не перехваченное в async-методе, помещается в объект Task, возвращаемый вызывающей программе. Если в этот момент вызывающая программа уже ждет объекта Task, то исключение будет возбуждено в точке ожидания. Таким образом, исключение передается вызывающей программе вместе со сформированной виртуальной трассировкой стека – точно так же, как в синхронном коде.

Я называю это виртуальной трассировкой стека, потому что стек – вообще-то принадлежность потока, а в асинхронной программе реальный стек текущего потока может не иметь ничего общего с трассировкой стека в момент исключения. В исключении запоминается трассировка стека, отражающая намерение программиста, в ней представлены те методы, который программист вызывал сам, а не детали того, как C# исполнял части этих методов в действительности.

Асинхронные методы до поры исполняются синхронно Выше я уже отмечал, что async-метод становится асинхронным , только встретив вызов асинхронного метода внутри оператора await . До этого момента он работает в том потоке, в котором вызван, как обычный синхронный метод.

Иногда длительная операция не отправляет запросов в сеть и не обращается к диску, а просто выполняет продолжительное вычисление.
Разумеется, нельзя рассчитывать на то, что поток быстро выполнит, желательно избежать зависания пользовательского интерфейса.
Для этого мы должны вернуть управление потоку пользовательского интерфейса, чтобы он мог обрабатывать другие события, а длительное вычисление производить в отдельном потоке.

Класс Task позволяет это сделать, а для обновления пользовательского интерфейса по завершении вычисления мы можем, как обычно, использовать await:

Метод Task.Run исполняет переданный ему делегат в потоке, взятом из пула ThreadPool. В данном случае я воспользовался лямбда-выражением, чтобы упростить передачу счетной задаче локальных переменных. Возвращенная задача Task запускается немедленно, и мы можем дождаться ее завершения, как любой другой задачи:

Это очень простой способ выполнить некоторую работу в фоновом потоке.
Если необходим более точный контроль над тем, какой поток производит вычисления или как он планируется, в классе Task имеется статическое свойство Factory типа TaskFactory. У него есть метод StartNew с различными перегруженными вариантами для управления вычислением:

В разделе «Task и await» выше мы видели, как просто организовать выполнение несколько параллельных асинхронных задач, – нужно запустить их по очереди, а затем ждать завершения каждой. Потом мы узнаем, что необходимо дождаться завершения каждой задачи,
иначе можно пропустить исключения.

Для решения этой задачи можно воспользоваться методом Task.
WhenAll , который принимает несколько объектов Task и порождает агрегированную задачу, которая завершается, когда завершены все исходные задачи. Вот простейший вариант метода WhenAll (имеется также перегруженный вариант для коллекции универсальных объектов Task ):

Task WhenAll (IEnumerable tasks)

Основное различие между использованием WhenAll и самостоятельным ожиданием нескольких задач, заключается в том, что WhenAll корректно работает даже в случае исключений. Поэтому старайтесь всегда пользоваться методом WhenAll .

Универсальный вариант WhenAll возвращает массив, содержащий
результаты отдельных поданных на вход задач Task. Это сделано скорее для удобства, чем по необходимости, потому что доступ к исходным объектам Task сохраняется, и ничто не мешает опросить их свойство Result , так как точно известно, что все задачи уже завершены.

Часто возникает также ситуация, когда требуется дождаться завершения первой задачи из нескольких запущенных. Например, так бывает, когда вы запрашиваете один и тот же ресурс из нескольких источников и готовы удовлетвориться первым полученным ответом.

Task > WhenAny (IEnumerable > tasks)

В ситуации, когда возможны исключения, пользоваться методом WhenAny следует с осторожностью. Если вы хотите знать обо всех исключениях, произошедших в программе,
то необходимо ждать каждую задачу, иначе некоторые исключения могут быть потеряны. Воспользоваться методом WhenAny и просто забыть об остальных задачах – всё равно, что перехватить все исключения и игнорировать их.
Это достойное порицания решение, которое может впоследствии привести к тонким ошибкам и недопустимым состояниям программы.

Метод WhenAny возвращает значение типа Task >. Это означает, что по завершении задачи вы получаете объект типа Task .
Он представляет первую из завершившихся задач и поэтому гарантированно находится в состоянии «завершен». Но почему нам возвращают объект Task, а не просто значение типа T?

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

Task > anyTask = Task.WhenAny(tasks);
Task winner = await anyTask;
Image image = await winner; // Этот оператор всегда завершается синхронно

AddAFavicon(image);
foreach (Task eachTask in tasks)
<
if (eachTask != winner)
<
await eachTask;
>
>

Нет ничего предосудительного в том, чтобы обновить пользовательский интерфейс, как только будет получен результат первой завершившейся задачи (winner), но после этого необходимо дождаться остальных задач, как сделано в коде выше. При удачном стечении обстоятельств все они завершатся успешно, и на выполнении программы это никак не отразится. Если же какая-то задача завершится с ошибкой, то вы сможете узнать причину и исправить ошибку.

Отмена асинхронных операций связывается с типом CancellationToken

По соглашению всякий метод, поддерживающий отмену, должен иметь перегруженный вариант, в котором за обычными параметрами следует параметр типа CancellationToken

При вызове метода ThrowIfCancellationRequested отмененного объекта CancellationToken возбуждается исключение типа OperationCanceledException.

Библиотека Task Parallel Library знает, что такое исключение представляет отмену, а не ошибку, и обрабатывает его соответственно. Например, в классе Task имеется свойство IsCanceled , которое автоматически принимает значение true, если при выполнении async-метода произошло исключение OperationCanceledException.

Удобной особенностью подхода к отмене, основанного на маркерах CancellationToken , является тот факт, что один и тот же маркер можно распространить на столько частей асинхронной операции, сколько необходимо, – достаточно просто передать его всем частям.
Неважно, работают они параллельно или последовательно, идет ли речь о медленном вычислении или удаленной операции, – один маркер отменяет всё.

До первого await не происходит ничего интересного.
Async не планирует выполнение метода в фоновом потоке. Единственный способ сделать это – воспользоваться методом Task.Run , который специально предназначен для этой цели, или чем-то подобным.

В приложении с пользовательским интерфейсом это означает, что код до первого await работает в потоке пользовательского интерфейса.
А в веб-приложении на базе ASP.NET – в рабочем потоке ASP.NET.

Часто бывает, что выражение в строке, содержащей первый await, содержит еще один async-метод .
Поскольку это выражение предшествует первому await, оно также выполняется в вызывающем потоке.
Таким образом, вызывающий поток продолжает «углубляться» в код приложения, пока не встретит метод, действительно возвращающий объект Task .

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

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

Текущий контекст SynchronizationContext – это свойство текущего потока. Идея в том, что всякий раз, как код исполняется в каком-то специальном потоке, мы можем получить текущий контекст синхронизации и сохранить его.
Впоследствии этот контекст можно использовать для того, чтобы продолжить исполнение кода в том потоке, в котором оно было начато.

Поэтому нам не нужно точно знать, в каком потоке началось исполнение, достаточно иметь соответствующий объект SynchronizationContext .

В классе SynchronizationContext есть важный метод Post , который гарантирует, что переданный делегат будет исполняться в правильном контексте.

Мы знаем, что код, предшествующий первому await , исполняется в вызывающем потоке, но что происходит, когда исполнение вашего метода возобновляется после await ?

На самом деле, в большинстве случаев он также исполняется в вызывающем потоке, несмотря на то, что в промежутке вызывающий поток мог делать что-то еще.
Это существенно упрощает написание кода.

Для достижения такого эффекта используется класс SynchronizationContext .

Важно.
В момент приостановки метода при встрече оператора await текущий контекст SynchronizationContext сохраняется.
Далее, когда метод возобновляется, компилятор вставляет вызов Post , чтобы исполнение возобновилось в запомненном контексте.

В классе Task имеется свойство Result , обращение к которому блокирует вызывающий поток до завершения задачи.

Его можно использовать в тех же местах, что await , но при этом не требуется, чтобы метод был помечен ключевым словом async или возвращал объект Task .
И в этом случае один поток занимается – на этот раз вызывающий (то есть тот, что блокируется).

Большинство написанных вами async-методов возвращают значение типа Task или Task . Цепочка таких методов, в которой каждый ожидает завершения следующего, представляет собой асинхронный аналог стека вызовов в синхронном коде.

Компилятор C# прилагает максимум усилий к тому, чтобы исключения, возбуждаемые в этих методах, вели себя так же, как в синхронном случае. В частности, блок try-catch , окружающий ожидаемый async-метод, перехватывает исключения, возникшие внутри этого метода


async Task Catcher()
<
try
<
await Thrower();
>
catch (AlexsException)
<
// Исключение будет обработано здесь
>
>

async Task Thrower()
<
await Task.Delay(100);
throw new AlexsException();
>

Для этого C# перехватывает все исключения, возникшие в вашем async-методе . Перехваченное исключение помещается в объект Task , который был возвращен вызывающей программе. Объект Task переходит в состояние Faulted. Если задача завершилась с ошибкой, то ожидающий ее метод не возобновится как обычно, а получит исключение, возбужденное в коде внутри await .

В async-методе оно возбуждается там, где находится оператор await , а не в точке фактического вызова метода. Это становится очевидным, если разделить вызов и await .

Интеграция Native кода в C# проект

Сегодня стала очень популярной платформа .Net Framework и язык C#. На ней пишутся приложения, игры, инструменты. Однако существует много кода написанного на C++, который часто нужно использовать в C# проектах.

Это могут быть математические библиотеки, код оставшийся от прошлых проектов или другой проект, написанный на C++ для которого нужно сделать графический интерфейс.

В этой статье речь пойдет о том, как интегрировать Native код в C# проект и успешно его развивать. В качестве IDE будет использоваться Microsoft Visual Studio.

Настройка проектов

На прямую вызывать C++ код из C# нельзя. Так же нельзя подключить к C# проекту native проект. Поэтому нам понадобится дополнительная прослойка — Managed C++ проект. Этот проект создаст Managed dll, которая будет затем подключена в основное приложение. Смысл этого проекта в том, что он может содержать как managed код так unmanaged код, т.е. код на обычном C++. Более того, он может подключать обычный native проект и использовать его код.

Рассмотрим интеграцию C++ проекта в C# проект на простом примере. Создадим солюшен с Windows Forms проектом. Это будет наше приложение. Нам нужно подключить к нему native проект.

Для начала подключим его к салюшену и укажем чтобы он собирался как Static library. Это можно указать в настройках проекта.

Как уже было сказано, мы не можем напрямую использовать этот код в нашем приложении. Поэтому создадим дополнительный проект, который будет связующим звеном. Создаем его обычными средствами, как простой C++ проект.

При создании проекта указываем, чтобы его код компилировался в dll. Кроме того указываем, чтобы это был пустой проект.

После создания проекта нужно указать что это managed проект. Для этого в его настройках нужно выбрать параметр /crt. Именно этот флаг скажет компилятору, что нужно сделать managed сборку.

В этот проект можно подключать другие managed сборки. Их нужно указать в разделе Reference. В разделе Project Dependencies. выберите native проект, который мы подключили раньше.

Ну и не забудьте подключить Managed C++ проект к C# проекту через Reference. Настройка проекта завершена. Теперь вы можете написать код на Managed C++, который будет использовать С++ код напрямую. Это будет обертка управляемого кода, которую можно будет использовать в C#.

Использование native кода в Managed C++

Класс написанный на Managed C++ может напрямую использовать С++ код. Экземпляр управляемого класса может содержать указатель на не управляемый класс.

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

Совет: Не стоит делать обертки для всех native классов. Значительно лучше выносить только высоко-уровневые функции, чтобы инкапсулировать всю функциональность в native коде.

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

Отладка

Одной из проблем при разработке приложений является их отладка. По умолчанию нет возможности при отладке спускаться по стеку в native код. Однако можно включить в настройках проекта опцию Enable unmanaged code debugging и при отладке можно будет остановить приложение с просматривать callstack от native кода.

Однако это приведет к некоторому снижению производительности, поэтому имеет смысл создать дополнительную build-конфигурацию и собирать ее в тот момент когда нужно дебажить native код.

Нехитрые Советы для программиста C#

1. Довольно часто нужно вернуть в коде истину , если число положительное , и ложь , если число отрицательное. Тогда , вместо того , чтобы писать что-то вроде этого :

public static bool TruOrFls (int i )

. можно написать такой простой код :

public static bool TruOrFls( int i)

2. Для определения , является ли целое число чётным или нечётным , можно использовать сл. метод :

public static bool IsNechet( int i)

Math .DivRem(i, 2, out result);

Цукерберг рекомендует:  Frontend - Требования к Junior разработчику фронтенда

3. Простой класс для Шаблона «Одиночка » (Singleton) для создания форм :

public static class SingletonForm

private static Dictionary Type , Form > forms = new Dictionary Type , Form >();

public static Form ShowInstance( Type t)

Form f = ( Form ) Activator .CreateInstance(t);

f.Disposed += new EventHandler (f_Disposed);

static void f_Disposed( object sender, EventArgs e)

public void btnsprTIP_Click(System. Object sender, System. EventArgs e)

SingletonForm .ShowInstance( typeof ( frmMyForm ));

4 . Для «отлавливания» неверно введённого символа в сетке( DataGr >можно использовать событие DataError :

public void MyDtg_DataError( object sender, DataGridViewDataErrorEventArgs anError)

int ColIndx = ( int )anError.ColumnIndex;

string ColHT = MyDtg.Columns[ColIndx].HeaderText;

Utils .Mess( » » + ColHT + » !» );

5. Помещайте формы , классы , отчёты и т д. проекта в соответствующие подкаталоги с именами , например , FRM, CLS, RPT и т.д. Такая систематизация существенно облегчит вашу жизнь при работе с большими проектами. Мешанина из классов , форм , отчётов и пр. в одном подкаталоге вряд ли пойдёт на пользу качеству вашего проекта.

6. Если нужно постоянно обращаться к какому-то объекту ( или что-то возвращать из метода без передачи параметра внутрь его) , то такую функцию удобно осуществлять с помощью аксессора get . Например , сл. код возвращает текущую строку в DataGrid View:

public DataGridViewRow GetCurrentRowOb

К такому методу можно обращаться просто как к переменной.

7. Вы можете назначить свою обработку какого-либо события в конструкторе сл. образом :

/* Альтернативная обработка события */

this .txtHr.KeyPress += new KeyPressEventHandler ( this .ObrabKey);

В приведённом примере назначаем метод обработки ObrabKey событию KeyPress

8. Технология LINQ может существенно сократить количество ваших клацаний по клавиатуре. Например сл. код возвращает отсортированный массива :

public static string [] GetStr

public static IEnumerable string > SortedList

IEnumerable string > sort = GetStr.OrderBy(a => a.j);

9. Несложные классы (особенно если используется их большое количество экземпляров) лучше оформлять в виде структур — ключевое слово struct . Структура , в отличии от класса , является значимым типом. Экономия ресурсов , в этом случае , может оказаться весьма существенной.

10 . Довольно часто приходиться итерировать объекты , которые не поддерживают интерфейс IEnumarable. Чтобы избежать это ограничение , существует специальная конструкция — yeild. Ниже приведён пример , как можно проитерировать коллекцию упр. элементы на главной форме :

class Num : IEnumerable

frmMain MyF = GlobalVar .MainForm;

public System.Collections. IEnumerator GetEnumerator()

yield return MyF.Panel1;

yield return MyF.Panel2;

yield return MyF.Panel3;

yield return MyF.panel6;

yield return MyF.GroupBox1;

yield return MyF.groupBox2;

yield return MyF.GroupBox3;

yield return MyF.TableLayoutPanel1;

yield return MyF.pictureBox1;

Вот теперь можно пробежаться в цикле -применение в вызывающей стороне :

foreach ( Control cob in new Num ())

cob.Anchor = AnchorStyles .Right;

11. Использование конструкции using ( . ) < . >гарантирует применение метода . Dispose() — освобождение объекта , например :

using ( SqlCommand cmd = new SqlCommand (strSQL, GlobalVar .conn))

// Здесь что-то делаем с объектом cmd .

// Это нужно делать , если не использовать using (. )

1 2 . При работе с базами данных обязательно напишите класс с методами создания соединения с БД и его тестированием , что-то вроде этого :

static class clsConn

public static void CreateConn( string strmydir)

GlobalVar .conn = new SqlConnection (ConnectStringBuild);

Utils .Mess( » Ошибка !» );

public static bool TestConn

using ( SqlConnection testconn = new SqlConnection (ConnectStringBuild))

return (testconn.State == ConnectionState .Open);

// Создание строки подключения к БД .

public static string ConnectStringBuild

string s = «Data Source=» + My. Settings .Default.ServerName + @»\» + My. Settings .Default.SQLServerName + «;» ;

return @s += «Initial Catalog=» + My. Settings .Default.BaseName + «;Integrated Security=True» ;

13. Включите обработку ошибок в стартовый модуль вашей программы. Выше него ошибка » не поднимется «( выше только Господь Бог :-). Что-то вроде этого :

sealed class mdlMain

public static void Main( string [] args)

Application .SetCompatibleTextRenderingDefault( false );

GlobalVar .MainForm = new frmMain ();

Application .Run( GlobalVar .MainForm);

catch ( ArgumentNullException ane)

MessageBox .Show(ane.Message, » Main-ArgumentNullException» );

catch ( FormatException fex)

MessageBox .Show(fex.Message, » Main-FormatException» );

catch ( NullReferenceException nre)

MessageBox .Show(nre.Message, » Main-NullReferenceException» );

catch ( InvalidCastException ice)

MessageBox .Show(ice.Message, » Main-InvalidCastException» );

catch ( Exception ex)

MessageBox .Show(ex.Message , » o Main» );


14. Если в программе постоянно приходиться обрабатывать неизменяемый массив строк , то инициализацию такого массива очень удобно оформить с помощью аксессора get ( см. Совет 6 ) , что-то вроде этого :

private static string [] GetStringArr

string [] ars = new string [] <

Тогда подсчитать , например , количество элементов в массиве очень просто :

int RecCount1 = GetStringArr.Count();

Или , обращение к j- му элементу массива :

string s = GetStringArr[j-1];

15. Довольно часто в коде нужно контролировать — является ли значение пустым , и вернуть , ну допустим , значение нуль для типа float. Осуществить такую проверку можно с помощью такого нехитрого метода :

public static float CheckIt( object par)

return ( Convert .ToSingle( Convert .IsDBNull(par) ? 0 : par));

16. Если нужно знать , в каком столбце сетки DataGridView пользователь завершил редактирование ячейки , то можно использовать событие _CellEndEdit:

public void MyDtg_CellEndEdit( object sender, DataGridViewCellEventArgs e)

// Узнаём имя столбца .

string ColName = MyDtg.Columns[e.ColumnIndex].Name;

/* Здесь что-то делаем . */

17. Чтобы ‘ пробежаться ‘ по всем контролам в панели , и отловить , ну скажем , метки ( Label ) можно использовать такой код :

foreach ( Control cob in this .panel 1 .Controls)

if (cob is Label )

/* Здесь что-то делаем с контролом типа Label */

18. К строковой переменной можно смело «приклеивать » целочисленные переменные. Среда CLR поймёт такое преобразование без метода ToString(). Это особенно бывает удобно при формировании SQL— запросов в виде строк , что-то вроде этого :

string sSQL= «Update Itogi SET Area=@area where kod=» + (i + 1);

19. ‘Склеивать’ строки лучше специально созданным для этого методом Concat для переменных типа string :

string s3 = «three» ;

// Можно делать так :

string sAll = s1+s2+s3;

// . но грамотнее так :

string sAll = string .Concat(s1,s2,s3);

20. П рограммисты как-то недооценивают удобство метода Format для строк при формировании SQL-запросов . Например, вместо того, чтобы писать что-то вроде этого:

// Можно делать так :

string strUpdate = «Update Customers SET Balance=@Balance Where Cust > + >» and Social > + SocID;

. лучше написать такой код:

string strUpdate = «Update Customers SET Balance=@Balance Where Cust >;

string strSQLUpdate = string. Format( «<0> <1>and Social >, strUpdate, ID, SocID);

21. Иногда бывает нужно узнать индекс элемента в массиве, ну например, в цикле foreach . Сделать это можно сл. образом :

// Есть массив строк .

private static string [] GetStr

string [] ars = new string [] <

// В вызывающей стороне :

// Конечно , можно использовать счётчик .

foreach ( string s in GetStr)

private static void Sub()

foreach ( string s in GetStr)

//Узнаём индекс элемент а s в массиве .

int i = Array .IndexOf(GetStr, s);

22. Везде, где можно, предпочтительнее использовать типизированные коллекции, вместо классических массивов. Основные преимущества такого подхода заключаются в двух фактах: во-первых, коллекции позволяют не заботиться о контроле за выходом индекса за установленный размер ( пресловутое Index Out of Range), а во-вторых, в них можно хранить (в отличии от нетипизированных списков) элементы одного типа. Итак, вместо использования скажем:

string [] ars = new string [6];

. предпочтительнее использовать типизированный список List string >:

List string > ars = new List string >();

Небольшая ремарка. Вообще , на мой взгляд , в связи с появлением начиная , по-моему , с . NET Framework 2.0 типизированных коллекций , классические массивы можно считать коллекциями устаревшего типа по причинам , указанным выше

23. Если в метод надо передавать много-много всяких входных параметров, то вместо такого «баяна» предпочтительнее передавать структуру.

Например, вместо использования некоего метода XYZ

static void XYZ( int a, int b, int c, string d, string e)

. .. можно сначала определить структуру:

public string d,e;

public ABC ( int _a, int _b, int _c, string _d, string _e)

a=_a; b=_b; c=_c; d=_d; e=_e;

. а затем передавать её в метод :

static void XYZ( ABC mystru)

Вызов такого метода станет гораздо более изящным:

/* Определяем экземпляр структуры ABC */

ABC MyStru = new ABC (1,2,3,»one», «two»);

/*Вызываем метод XYZ */

24. Довольно часто программисты пользуются методом Parse для конвертации строковых величин в другие типы, что-то вроде этого:

int j= int .Parse (MyVal.ToString());

Однако, в случае неудачи такой метод выбросит исключение. Для написания устойчивого кода предпочтительнее пользоваться метод TryParse, приблизительно так:

/* Вернёт значение j=0, даже если метод TryParse потерпит неудачу */

bool bVal = int .TryParse(MyVal.ToString(), out j);

25 . Самый подходящий элемент для отображения содержания базы данных — это конечно же DataGr >, упрощенно говоря, сетка данных . Но после завершения редактирования данных пользователем в такой сетке встаёт задача — как передать изменения в базу данных ? Одно из решений такой задачки состоит в переписывании (в цикле естественно) значений ячеек сетки DataGridViewCell в значения объекта DataRow , привязанного к сетке объекта DataTable набора данных DataSet . Тут, правда, надо хорошо разбираться в идеологии объектной модели доступа к данным ADO. NET . Затем значения DataRow конкретного столбца переписываем SQL-конструкцией Update непосредственно в базу данных. Все циклы здесь можно прогонять конструкцией foreach

26 . Иногда возникает сл. ситуация. На форме есть куча однотипных элементов WinForms, одно и тоже событие которых обрабатывается одинаково. Как пример, есть несколько элементов TextBox, для которых событие, ну скажем, KeyPress обрабатывается одним и тем же методом.

Тогда, вместо такого забойного кода . :

public void txt box1 _KeyPress( object sender, KeyPressEventArgs e)

ObrabKeyPress ( sender, e );

public void txt box 2_KeyPress( object sender, KeyPressEventArgs e)

ObrabKeyPress ( sender, e );

public void txt boxN _KeyPress( object sender, KeyPressEventArgs e)

ObrabKeyPress ( sender, e );

. можно сначала определить массив TextBox’ов :

private TextBox [] RetTBox

. а затем, скажем в конструкторе формы, прокрутить такой цикл :

/* Добавляем свой обработчик контроля нажатия клавиш */

foreach ( TextBox tb in RetTBox)

tb.KeyPress += new KeyPressEventHandler (ObrabKeyPress);

27 . У объекта ADO.Net Reader есть один весьма полезный метод — HasRows. Он возвращает значение true , если в запросе есть данные.

Проконтролировать наличие ‘ пустого ‘ Reader’ а в это случае становиться довольно легко :

using ( SqlCommand SelCustoms = new SqlCommand (sSel, conn2))

//Нет записей о заказчиках ! Прекращаем читать ..

// Пробежимся по всем записям Заказчиков .

// . Здесь что-то читаем Reader’ом

28 . Хорошей альтернативой созданию параметров в простых SQL- запросах ( объект Parameters) может стать использование строки запроса методом string .Format ( см. Совет 20 ) , что-то вроде этого :

string sSQL = string .Format( «Select * From Customers where Customer >, CustID);

SqlCommand cmd = new SqlCommand (sSQL, conn2)

Объём кода при этом значительно сокращается — необходимость описывать параметр , его тип и значение просто отпадает.

2 9 . Приступая к созданию своего первого более-менее сложного проекта , не забудьте создать класс для размещения в нём всевозможных мелких вспомогательных утилит. Фактически , наличие такого класса (как правило с именем Utils ), стало стандартом для проектирования информационных систем на С #. Получиться что-то вроде аналог ящика для инструментов , из которого вы сможете время от времени доставать нужное (по контексту ситуации) приспособление.

30 . Создание глобальных переменных , видимых из любой части программы , является одним из стандартных положений для разработчика любого уровня . Чаще всего , такая необходимость связана с использованием каких-то глобальных настроек программы. Одним из решений этой задачи является использование настроек — пространство имён My. Setting . Тогда достаточно будет написать , ну скажем в классе Utils ( см. Совет 29 ) , простой метод для ‘ выдёргивания ‘ значения этой настройки , что-то вроде этого (в приведённом ниже примере , если уж рассматривать совсем конкретный случай , просто взят год , принятый по умолчанию) :

public static int GetGodSetting

return ( int .Parse)(My.Settings. Default .god);

В любом месте программы для загрузки года , принятого в настройках по умолчанию , можно использовать такое простое обращение :

int God = Utils .GetGodSetting;

31 . При использовании сложных классических WinForms, старайтесь не перегружать код формы. Например , если на форме есть много кнопок , нажатие которых надо обрабатывать , вместо такого кода (особенно если он забойный) . :

public void Btn1_Click(System. Object sender, System. EventArgs e)

// Здесь что-то обрабатываем при нажатии на кнопку Btn1

public void Btn 2 _Click(System. Object sender, System. EventArgs e)

// Здесь что-то обрабатываем при нажатии на кнопку Btn 2

public void BtnN_Click(System. Object sender, System. EventArgs e)

// Здесь что-то обрабатываем при нажатии на кнопку BtnN

. можно создать класс ну скажем clsClickBtn , в который вынести все методы обработки при нажатии на кнопку , а в конструкторе формы разместить сл. код :

Btn1.Click += new EventHandler ( clsClickBtn .Btn1_Click);

Btn2.Click += new EventHandler ( clsClickBtn .Btn2_Click);


BtnN.Click += new EventHandler ( clsClickBtn .BtnN_Click);

Код формы в этом случае может сильно ‘ похудеть ‘ .

04. 12 .2013 , г. Юрьевец

32 . Ещё один подход к обозначенной в п. 31 проблеме может быть следующий.

Сначала создаём структуру ну допустим для обработки нажатий на гиперссылку LinkLabel :

public LinkLabel l;

public LinkLabelLinkClickedEventHandler h;

public struLL( LinkLabel _l, LinkLabelLinkClickedEventHandler _h)

Далее создаём список из обозначенной выше структуры , что-то вроде такого :

private List struLL > GetLLList

List struLL > opList = new List struLL >();

opList.Add( new struLL (lL _ A, clsLLblClick .MethodA));

opList.Add( new struLL (lL _ B, clsLLblClick .MethodB));

opList.Add( new struLL (lL _ Z, clsLLblClick .MethodZ));

Для инициализации методов обработки при нажатии на гиперссылки (в случае нашего примера) осталось только в конструкторе формы прокрутить такой цикл :

foreach ( struLL ll in GetLLList)

3 3 . Каким образом можно проитерировать управляющие элементы (контролы в просторечии) на форме ? Один из подходов описан в Совете 26 . Но существует ещё одно решение , пожалуй даже более изящное и простое. Заключается оно в том , что нужные контролы просто помещаем на форме в какой-либо контейнер , типа Panel . В этом случае в конструктор формы помещаем такой простой код (как пример для Кнопок — объект Button ) для обработки нажатий :

// Цикл по кнопкам в панели 1 .

foreach ( Button btn in panel 1 .Controls)

btn.Click += new EventHandler (btnClik);

34. При работе со справочниками часто требуется вернуть значение кода (ключа) по значению описания ключа. Конечно , можно для решения такой задачки создать структуру с двумя полями. Но гораздо проще воспользоваться одной из уже существующих коллекций — например словарем Dictionary . Тогда указанный подход можно продемонстрировать на сл. примере.

Создаем следующий метод :

public static int ReturnKodTown( string s)

Dictionary int, string > DicTowns = new Dictionary int, string >();

DicTowns.Add( 2 , » Санкт-Петербург » );

DicTowns.Add( 3 , » Казань » );

DicTowns.Add( 4 , » Новосибирск » );

DicTowns.Add( 5 , » Краснодар » );

return DicTowns.First(a => a.Value == s).Key;

Теперь узнать код города для нашего примера очень легко :

string gorod = » Новосибирск » ;

int kod = ReturnKodTown (gorod);

35. При использовании параметризованных SQL- запросов предпочтительнее пользоваться более современным методом . AddWithValue вместо .Add. Указывать тип поля при таком подходе не обязательно , нужно только указать параметр и его значение :

var myCMD = new SqlCommand (strInsSvod, GlobalVar.conn)

var pars = myCMD.Parameters;

// Используем метод AddWithValue

pars.AddWithValue( «@myPar» , myVal);

36. В развитии темы использования более рациональных подходов создания параметризованных запросов ( Совет 35 ). У коллекции параметров Parameters объекта Command есть ещё один полезный метод , позволяющий загрузить п a раметры в запрос массивом. Называется он . AddRange :

// Готовим массив параметров .

private static SqlParameters [] GetMyPars

new SqlParameter ( «@par2» , myVal2)),

new SqlParameter ( «@parN» , myValN))

// Использование метода . AddRange

using ( var cmd = new SqlCommand (s, GlobalVar.conn))

var pc = cmd.Parameters;

3 7 . При инициализации коллекций типа List вместо такого кода :

List string > MyList = new List string >();

MyList. Add( » один » ) ;

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

3 8 . При работе с коллекциями иногда хотелось бы не только проитерировать их в одном операторе , но и совершить какую-то обработку. В качестве примера , предположим , что есть массив :

. и для значений массива меньше или равно 5 , нужно умножить на 2 , а для более 5 умножить на 3.

Здесь на помощь может придти делегат Func и блочный лямбда-оператор. Что-то вроде этого ( понятное дело , блочный лямбда-оператор может быть много сложнее и состоять не из одной строки , как в случае нашего простенького примера) :

Func int , int , int > myFunc = (z, y) => <

return ( z y) ? 2 * z : 3 * z ;

Теперь , чтобы получить массив обработанных значений gruppa , можно написать такой простой код одной строчкой ( никакого foreach не нужно ! ) :

var gruppa = myInt.Select(a => myFunc(a, 5)).ToArray();

10 .01. 2015 , г. Обнинск

40. Назначить свой обработчик событий, скажем, для ввода данных в TextBox можно еще и с использование лямбда-оператора сл. образом ( м етод MyObrabKeyPress ) :

textBox1.KeyPress += (o, e) => MyObrabKeyPress( o, e);

Тогда , даже можно создать свой метод для обработки нажатий в нескольких контролах , что-то вроде такого :

textBox1.KeyPress += (o, e) => MyObrabKeyPress( o, e);

textBox2.KeyPress += (o, e) => MyObrabKeyPress( o, e);

textBox3.KeyPress += (o, e) => MyObrabKeyPress( o, e);

textBoxN.KeyPress += (o, e) => MyObrabKeyPress( o, e);

Затем , такой метод лучше всего поместить в конструктор формы , на которой расположены наши контролы ( TextBox’ ы для случая нашего примера) :

41. Тем , кто создает приложения для работы с базами данных, случается иметь дело с весьма ‘забойными’ SQL-запросами, которые могут занять десятки строчек кода. Но хранить такие запросы в тексте программы, несмотря на определенное удобство для разработчика (код запроса виден прямо в теле программы) не всегда изящно по причине необходимости ‘ сцепки ‘ строчек в запросе. И здесь возможно два варианта решения задачи хранения ‘ тяжелых ‘ запросов. Первый заключается в перемещении запроса непосредственно в базу данных в виде хранимой процедуры ( Store Procedure) . Второй путь связан с перемещением скрипта в строковую переменную в Настройки ( Setting, см. п.30 )

42. В продолжении темы п.41 . В код программы на C# ‘запилить’ длинный SQL-запрос можно используя символ литерала ‘@’. В этом случае отпадает необходимость в конкатенации строк. Выглядит такая конструкция может приблизительно сл. образом :

string mySql = @»Первая длинная строка запроса

Вторая ну очень длинная строка запроса

Третья длинная строка запроса

Последня я строка ну очень длинного запроса» ;

Модель памяти C# в теории и на практике

Продукты и технологии:

C#

В статье рассматриваются:

  • спецификация языка C#;
  • переупорядочение операций с памятью;
  • шаблоны взаимодействия потоков;
  • interlocked-операции и барьеры памяти.

Это первая из двух статей, в которых пойдет долгое повествование о модели памяти в C#. В первой части поясняются гарантии модели памяти C# и показываются шаблоны кода, которыми обусловлены эти гарантии; во второй части будет подробно рассмотрено, как данные гарантии достигаются на разных аппаратных архитектурах в Microsoft .NET Framework 4.5.

Одна из причин сложности многопоточного программирования в том, что компилятор и аппаратное обеспечение могут слегка трансформировать операции программы с памятью такими способами, которые не влияют на однопоточное поведение, но могут затронуть многопоточное. Рассмотрим следующий метод:

Если _data и _initialized — обычные (т. е. неизменяемые) поля, компилятору и процессору разрешается такое переупорядочение операций, чтобы Init выполнялся так, будто он написан следующим образом:

Существуют различные оптимизации как в компиляторах, так и в процессорах, которые могут привести к такому переупорядочению, и это будет обсуждаться во второй статье.

В однопоточной программе переупорядочение выражений в Init ничего не меняет в программе. Пока и _initialized и _data обновляются до возврата управления этим методом, порядок присваиваний не имеет значения. В однопоточной программе нет второго потока, который мог бы наблюдать состояние между этими обновлениями.

Даже если компилятору и процессору разрешено переупорядочивать операции с памятью, это не означает, что на практике они всегда так делают.

Но в многопоточной программе порядок присваиваний может иметь значение, поскольку другой поток может считывать эти поля, пока Init находится в середине выполнения. Соответственно в переупорядоченной версии Init другой поток может наблюдать _initialized=true и _data=0.

Модель памяти C# — это набор правил, описывающих, какие виды переупорядочения операций с памятью разрешены, а какие — нет. Все программы должны быть написаны в соответствии с гарантиями, определенными в спецификации.

Однако, даже если компилятору и процессору разрешено переупорядочивать операции с памятью, это не означает, что на практике они всегда так делают. Многие программы, которые содержат «ошибку» согласно абстрактной модели памяти C#, будут по-прежнему корректно выполняться на конкретном аппаратном обеспечении, где работает определенная версия .NET Framework. В частности, процессоры x86 и x64 переупорядочивают операции лишь в некоторых сценариях весьма узкого применения, и аналогично JIT-компилятор в CLR не выполняет многие трансформации, которые ему разрешены.

Модель памяти C# разрешает переупорядочение операций в каком-либо методе, только если поведение при однопоточном выполнении не меняется.

Хотя абстрактная модель памяти C# — это то, что вы учитывать при написании нового кода, понимание истинной реализации модели памяти в различных аппаратных архитектурах может оказаться весьма полезным, в частности если вы пытаетесь разобраться в поведении существующего кода.

Модель памяти C# согласно ECMA-334

Авторитетное определение модели памяти C# дано в Standard ECMA-334 C# Language Specification (bit.ly/MXMCrN). Давайте обсудим эту модель в том виде, как она определена в данной спецификации.

Переупорядочение операций с памятью Согласно ECMA-334, когда поток считывает в C# участок памяти, записанный другим потоком, «читатель» может увидеть устаревшее значение. Эту проблему иллюстрирует рис. 1.

Рис. 1. Код, подверженный риску переупорядочения операций с памятью

Допустим, что Init и Print вызываются параллельно (т. е. в разных потоках) в новом экземпляре DataInit. Если вы посмотрите код Init и Print, вам может показаться, что Print может выводить только «42» или «Not initialized». Но Print также может вывести «0».

Модель памяти C# разрешает переупорядочение операций в каком-либо методе, только если поведение при однопоточном выполнении не меняется. Например, компилятор и процессор могут переупорядочить операции метода Init так:

Это переупорядочение не изменило бы поведение метода Init в однопоточной программе. Однако в многопоточной программе другой поток мог бы считать значения полей _initialized и _data после того, как Init модифицировал одно поле, но не успел сделать это со вторым, а затем последующее переупорядочение может изменить поведение программы. В результате метод Print мог бы вывести «0».

Переупорядочение Init — не единственный возможный источник проблем в этом примере кода. Даже если операции записи в Init не переупорядочиваются, операции чтения в методе Print могут быть трансформированы:

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

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

Изменяемые поля Язык программирования C# предоставляет изменяемые поля (volatile fields), которые ограничивают то, как могут быть переупорядочены операции с памятью. В спецификации ECMA утверждается, что изменяемые поля предоставляют семантику получения-освобождения (acquire­release) (bit.ly/NArSlt).

Чтение изменяемого поля имеет семантику получения, т. е. эта операция не может быть переупорядочена с последующими операциями. Чтение изменяемого поля образует одностороннюю преграду: предшествующие операции могут проникать через нее, а последующие — нет. Возьмем такой пример:

Чтение 1 и чтение 3 — операции с неизменяемыми полями, а чтение 2 — операция с изменяемым полем. Чтение 2 нельзя переупорядочить с чтением 3, но можно — с чтением 1. В табл. 1 показаны допустимые переупорядочения в теле Foo.

Табл. 1. Допустимое переупорядочение операций чтения в AcquireSemanticsExample

int a = _a; // чтение 1

int b = _b; // чтение 2 (изменяемое поле)

int c = _c; // чтение 3

int b = _b; // чтение 2 (изменяемое поле)

int a = _a; // чтение 1

int c = _c; // чтение 3


int b = _b; // чтение 2 (изменяемое поле)

int c = _c; // чтение 3

int a = _a; // чтение 1

С другой стороны, операция записи в изменяемое поле имеет семантику освобождения, и поэтому ее нельзя переупорядочить с предыдущими операциями. Запись изменяемого поля образует одностороннюю преграду, как демонстрирует следующий пример:

Записи 1 и 3 — операции с неизменяемыми полями, а запись 2 — операция с изменяемым полем. Запись 2 нельзя переупорядочить с записью 1, но можно — с записью 3. В табл. 2 показаны допустимые переупорядочения в теле Foo.

Табл. 2. Допустимое переупорядочение операций записи в ReleaseSemanticsExample

_a = 1; // запись 1

_b = 1; // запись 2 (изменяемое поле)

_c = 1; // запись 3

_a = 1; // запись 1

_c = 1; // запись 3

_b = 1; // запись 2 (изменяемое поле)

_c = 1; // запись 3

_a = 1; // запись 1

_b = 1; // запись 2 (изменяемое поле)

Я вернусь к семантике получения-освобождения в подразделе «Публикация через изменяемое поле» далее в этой статье.

Атомарность Другая проблема C#, о которой нужно знать, заключается в том, что значения не обязательно записываются в память атомарно. Рассмотрим этот пример:

Если один поток повторно вызывает SetValue, а другой вызывает GetValue, то второй поток может наблюдать значение, которое никогда не записывалось первым потоком. Например, если первый поток попеременно вызывает SetValue со значениями Guid (0,0,0,0) и (5,5,5,5), то GetValue может наблюдать (0,0,0,5), (0,0,5,5) или (5,5,0,0), хотя ни одно из этих значений никогда не присваивалось через SetValue.

Причина такого «разрыва» в том, что присваивание _value = value не выполняется атомарно на аппаратном уровне. Аналогично чтение _value тоже не выполняется атомарно.

Спецификация ECMA по C# гарантирует, что следующие типы будут записываться атомарно: ссылочные типы, bool, char, byte, sbyte, short, ushort, uint, int и float. Значения других типов, включая пользовательские значимые типы, могут помещаться в память набором атомарных операций записи. В итоге поток-«читатель» мог бы наблюдать рваные значения, состоящие из частей различных значений.

Один из подвохов в том, что типы, которые обычно считываются и записываются атомарно (например, int), могут считываться и записываться не атомарно, если значение неправильно выровнено в памяти. В нормальных условиях C# гарантирует, что значения выравниваются в памяти правильно, но пользователь может переопределить это выравнивание с помощью класса StructLayoutAttribute (bit.ly/Tqa0MZ).

Оптимизации без переупорядочения Некоторые оптимизации компилятора могут вводить или исключать определенные операции с памятью. Так, компилятор может заменить повторяемые операции чтения какого-либо поля одним чтением. Аналогично, если код считывает поле и сохраняет значение в локальной переменной, а затем повторно считывает эту переменную, то компилятор мог бы вместо этого выбрать повторное чтение поля.

Поскольку ECMA-спецификация C# не исключает оптимизации без упорядочения, они предположительно разрешены. По сути, как будет рассматриваться во второй части, JIT-компилятор действительно выполняет оптимизации этих типов.

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

Шаблоны взаимодействия потоков

Цель модели памяти — обеспечить взаимодействие потоков. Когда один из потоков записывает значения в память, а другой — считывает из памяти, модель памяти диктует, какие значения может увидеть читающий поток.

Блокировка Это самый простой способ совместного использования общих данных между потоками. При правильном использовании блокировок вам, в общем, не нужно беспокоиться о дополнительном исследовании модели памяти.

Всякий раз, когда поток захватывает блокировку, CLR гарантирует, что этот поток увидит все обновления, выполненные тем потоком, который владел блокировкой ранее. Добавим блокировку к примеру из начала этой статьи, как показано на рис. 2.

Рис. 2. Взаимодействие потоков с блокировкой

Добавление блокировки, которую поочередно захватывают Print и Set, дает простое решение. Теперь Set и Print выполняются атомарно по отношению друг к другу. Выражение lock гарантирует, что тела Print и Set будут выполняться в некоем последовательном порядке, даже если они вызываются из нескольких потоков.

Схема на рис. 3 показывает один из вариантов последовательного порядка, который мог бы наблюдаться, если бы поток 1 вызвал Print три раза, поток 2 вызвал Set один раз, а поток 3 вызвал Print один раз.

Блокировка — универсальный и мощный механизм разделения общего состояния между потоками.

Рис. 3. Последовательное выполнение с блокировкой

_lock _lock
Thread 1 Поток 1
Thread 2 Поток 2
Thread 3 Поток 3
Print Print
Set Set

Когда выполняется порция кода, владеющая блокировкой, она гарантированно видит результаты всех предшествующих в последовательном порядке операций записи. Кроме того, она гарантированно не видит результаты любых операций записи, выполняемых теми порциями кода, которые следуют за блокировкой.

Если в двух словах, то блокировки исключают всю непредсказуемость и сложность модели памяти: вам не надо беспокоиться о переупорядочении операций с памятью при правильном использовании блокировок. Но именно при правильном использовании. Если блокировку использует только Print или только Set (либо Print и Set захватывают две разные блокировки), операции с памятью становятся переупорядочиваемыми и сложность модели памяти вновь возвращается.

Публикация через API потоков Блокировка — универсальный и мощный механизм разделения общего состояния между потоками. Публикация через API потоков является еще одним часто применяемым шаблоном в программировании параллельной обработки.

Публикацию через API потоков легче всего пояснить на примере:

Изучив предыдущий пример кода, вы, вероятно, ожидали, что на экран будет выведено «42». И интуиция вас не подвела. Этот пример кода гарантированно выводит «42».

Может быть, это удивительно, что об этом вообще приходится упоминать, но на деле возможны реализации StartNew, которые выводили бы «0» вместо «42», по крайней мере теоретически. В конце концов, два потока взаимодействуют через неизменяемое поле, поэтому операции с памятью могут быть переупорядочены. Этот шаблон показан на схеме на рис. 4.

Рис. 4. Два потока, взаимодействующие через неизменяемое поле

Initial State Начальное состояние
Thread 1 Поток 1
Thread 2 Поток 2

Реализация StartNew должна гарантировать, что запись в s_value в потоке 1 не будет перемещена за , а чтение из s_value в потоке 2 не будет перемещено до . И действительно StartNew API это гарантирует.

Все остальные API потоков в .NET Framework, такие как Thread.Start и ThreadPool.QueueUserWorkItem, тоже дают аналогичные гарантии. По сути, почти каждый API потоков должен иметь некую семантику барьера, чтобы правильно работать. Это почти никогда не документируется, но обычно поддается логическому вычислению, если просто поразмыслить о том, какие гарантии должны быть у конкретного API, чтобы от него была польза.

Публикация через инициализацию типа Другой способ надежной публикации некоего значения нескольким потокам — его запись в статическое поле в статическом инициализаторе или в статическом конструкторе. Возьмем пример:

Если Test3.PrintValue одновременно вызывается из нескольких потоков, гарантируется ли, что каждый вызов PrintValue выведет «42» и «false»? Или же один из вызовов приведет к выводу «0» или «true»? Как и в предыдущем случае, вы получаете именно то поведение, которое ожидаете: каждый поток гарантированно выводит «42» и «false».

Обсуждавшиеся до сих пор шаблоны ведут себя ожидаемым образом. Теперь перейдем к случаям, где поведение может оказаться неожиданным.

Публикация через изменяемое поле Многие параллельные программы можно создать на основе уже рассмотренных трех простых шаблонов в сочетании с параллельными примитивами в .NET-пространствах имен System.Threading и System.Collections.Concurrent.

Шаблон, который я намерен обсудить, столь важен, что для него была разработана семантика ключевого слова volatile. По сути, лучший способ запомнить семантику ключевого слова volatile — запомнить этот шаблон, а не пытаться зазубривать абстрактные правила, пояснявшиеся ранее в этой статье.

Начнем с примера кода на рис. 5. В классе DataInit на рис. 5 два метода: Init и Print; оба могут быть вызваны из нескольких потоков. Если никакие операции с памятью не переупорядочиваются, Print может вывести только «Not initialized» или «42», но есть два возможных случая, когда Print мог бы вывести «0»:

  • операции записи 1 и 2 были переупорядочены;
  • операции чтения 1 и 2 были переупорядочены.

Рис. 5. Использование ключевого слова volatile

Если бы _initialized не была помечена как volatile, оба переупорядочения были бы разрешены. Однако, когда _initialized помечена как volatile, ни одно из этих переупорядочений не разрешено! В случае записи вы получаете обычную запись, за которой следует запись в изменяемое поле, а последнюю нельзя переупорядочить с предыдущей операцией в памяти. В случае чтения операция чтения из изменяемого поля сменяется обычной операцией чтения, а первую нельзя переупорядочить с последующей операцией в памяти.

Поэтому Print никогда не выведет «0», даже если она будет вызвана одновременно с Init в новом экземпляре DataInit.

Заметьте: если бы поле _data field было изменяемым, а _initialized — нет, оба переупорядочения были бы разрешены. В итоге этот пример является отличным способом запомнить семантику ключевого слова volatile.

Отложенная инициализация Одна из распространенных вариаций публикации через изменяемое поле — отложенная инициализация (lazy initialization) (рис. 6).

Рис. 6. Отложенная инициализация

В этом примере LazyGet всегда гарантированно возвращает «42». Однако, если бы поле _box field не было помечено как volatile, LazyGet могла бы вернуть «0» по двум причинам: могли бы быть переупорядочены либо операции чтения, либо операции записи.

Чтобы еще больше акцентировать на этом ваше внимание, рассмотрим такой класс:

Теперь возможно (по крайней мере, теоретически), что PrintValue выведет «0» из-за проблемы с моделью памятью. Вот пример использования BoxedInt, где такое разрешается:

Так как экземпляр BoxedInt был опубликован неправильно (через неизменяемое поле _box), поток, который вызывает Print, может наблюдать частично сконструированный объект! И вновь, сделав поле _box изменяемым, вы устраните проблему.

Interlocked-операции и барьеры памяти Interlocked-операции являются атомарными и иногда используются для уменьшения блокировок в многопоточных программах. Рассмотрим простой класс-счетчик, безопасный в многопоточной среде:

Используя Interlocked.Increment, вы можете переписать программу так:

При использовании Interlocked.Increment данный метод должен выполняться быстрее, по крайней мере в некоторых аппаратных архитектурах. В дополнение к операциям приращения класс Interlocked (bit.ly/RksCMF) предоставляет методы для различных атомарных операций: добавления значения, замены значения по условию, замены значения и возврата исходного значения и т. д.

Все Interlocked-методы имеют одно очень интересное свойство: их нельзя переупорядочивать с другими операциями с памятью.

Цукерберг рекомендует:  Создаем свою модульную сетку по-быстрому.

Все Interlocked-методы имеют одно очень интересное свойство: их нельзя переупорядочивать с другими операциями с памятью. Операция, тесно связанная с Interlocked-методами, — Thread.MemoryBarrier, которую можно рассматривать как пустую Interlocked-операцию. Так же, как Interlocked-метод, Thread.MemoryBarrier нельзя переупорядочить с любой предыдущей или последующей операцией с памятью. Однако в отличие от Interlocked-метода Thread.MemoryBarrier не имеет побочного эффекта; он просто ограничивает переупорядочения.

Цикл опроса Это шаблон, который, как правило, не рекомендуется, но, к сожалению, часто используется на практике. Неправильный цикл опроса показан на рис. 7.

Рис. 7. Неправильный цикл опроса

В этом примере основной поток крутится в цикле, опрашивая конкретное неизменяемое поле. Вспомогательный поток тем временем присваивает значение этому полю, но основной поток никогда не увидит измененное значение.

А если бы поле _loop было помечено как volatile? Исправило бы это ситуацию? По общему согласию экспертов, компилятору не разрешается вытаскивать чтение изменяемого поля из цикла, но вопрос о том, дает ли спецификация ECMA на C# такую гарантию, остается открытым.

Рекомендации

  • Весь код, который вы пишете, должен полагаться только на гарантии спецификации ECMA C# и не использовать никакие детали реализации, пояснявшиеся в этой статье.
  • Избегайте ненужного использования изменяемых полей. По большей части блокировки или параллельные наборы (System.Collections.Concurrent.*) лучше подходят для обмена данными между потоками. В некоторых случаях изменяемые поля можно использовать для оптимизации параллельного кода, но вы должны измерять производительность, чтобы убедиться в том, что выигрыш перевешивает дополнительную сложность.
  • Вместо самостоятельной реализации шаблона отложенной инициализации с применением volatile-поля используйте типы System.Lazy и System.Threading.LazyInitializer.
  • Избегайте циклов опроса. Зачастую вместо таких циклов можно использовать BlockingCollection , Monitor.Wait/Pulse, события или асинхронное программирование.
  • По возможности используйте стандартные параллельные примитивы .NET вместо самостоятельной реализации эквивалентной функциональности.

С одной стороны, спецификация утверждает, что только изменяемые поля подчиняются семантике получения-освобождения (acquire-release semantics), чего вроде бы недостаточно для предотвращения изъятия операции чтения изменяемого поля из цикла опроса. С другой — пример кода в этой спецификации действительно опрашивает изменяемое поле, а это подразумевает, что операция чтения изменяемого поля не может быть изъята из данного цикла.

На аппаратных платформах x86 и x64 PollingLoopExample.Main будет, как правило, зависать. JIT-компилятор считает поле test1._loop только один раз, сохранит его значение в одном из регистров, а затем будет крутиться в цикле, пока значение в этом регистре не изменится, чего не произойдет никогда.

Однако, если тело цикла содержит некоторые выражения, JIT-компилятору, возможно, потребуется тот же регистр для других целей, поэтому каждая итерация может приводить к повторному чтению test1._loop. В итоге вы можете столкнуться с циклами в существующих программах, которые опрашивают неизменяемое поле и ухитряются нормально работать.

Параллельные примитивы Большая часть параллельного кода может выиграть от применения высокоуровневых параллельных примитивов, которые появились в .NET Framework 4. В табл. 3 перечислены некоторые из таких .NET-примитивов.

Табл. 3. Параллельные примитивы в .NET Framework 4

Тип Описание
Lazy<> Значения с отложенной инициализацией
LazyInitializer
BlockingCollection<> Наборы, безопасные в многопоточной среде
ConcurrentBag<>
ConcurrentDictionary
ConcurrentQueue<>
ConcurrentStack<>
AutoResetEvent Примитивы для координации выполнения в разных потоках
Barrier
CountdownEvent
ManualResetEventSlim
Monitor
SemaphoreSlim
ThreadLocal<> Контейнер, хранящий отдельное значение для каждого потока

Используя эти примитивы, зачастую можно избежать низкоуровневого кода, зависимого от модели памяти весьма сложным для понимания образом (через volatile и прочее).

В следующей части

На данный момент я описал модель памяти C# так, как она определена в спецификации ECMA C#, и рассмотрел наиболее важные шаблоны взаимодействия потоков, определяющих модель памяти.

Во второй части этой статьи я поясню, как модель памяти на деле реализуется в различных аппаратных архитектурах, что поможет в понимании поведения настоящих программ.

Игорь Островский (Igor Ostrovsky) — старший инженер-разработчик ПО в Microsoft. Работал над Parallel LINQ, Task Parallel Library и другими библиотеками и примитивами для распараллеливания кода в Microsoft .NET Framework. Ведет блог по вопросам программирования на igoro.com.

Выражаю благодарность за рецензирование статьи экспертам Джо Даффи(Joe Duffy), Эрику Ейлебрехту(Eric Eilebrecht), Джо Хоугу(Joe Hoag), Эмаду Омара(Emad Omara), Гранту Рихинсу(Grant Richins), Ярославу Шевчуку(Jaroslav Sevcik) и Стефену Таубу (Stephen Toub).

Обучение C# (c sharp)

Данный видеокурс представлен для новичков в языке программирования C#. Если Вы решили, что готовы начать обучение программированию, то стартуйте вместе с нами! Курс программирования C# Стартовый поможет Вам начать Ваше знакомство с языком программирования C#. Узнайте основы современного программирования на Visual C # 2012 и приступите к созданию Вашего первого приложения на языке C#.

Этот видео урок помогает разобраться с двоичной и шестнадцатеричной системами счисления. Также Вы узнаете, какие простые типы данных есть в языке программирования C# и сможете правильно определять, какой тип данных следует использовать в конкретной ситуации.

Этот видео урок помогает разобраться с типами данных. Вы узнаете, какие простые типы данных есть в языке программирования C# и сможете правильно определять, какой тип данных следует использовать в конкретной ситуации.

На этом видео уроке Вы изучите условные операторы, позволяющие разрабатывать алгоритмы, в которых от выбора пользователя или от других условий будет зависеть результат. В этом уроке будут рассмотрены такие условные конструкции языка программирования C# — if/else, switch, тернарный оператор.

Данный видео урок рассматривает понятия логических и побитовые операции. На видеокурсе объясняется применение и использование операций конъюнкции, дизъюнкции, исключающего «или», логических операторов, операторов сдвига в языке программирования C#.

Видео урок полностью посвящен циклическим конструкциям — то, без чего не обойдется ни одна программа. В программировании часто встречаются действия, которые необходимо повторить несколько раз. Для решения подобных задач, в которых требуются неоднократное повторение операций, Вам необходимо разобраться с конструкциями, которые будут изучены в этом видео уроке.

Чтобы научится программировать на языке C#, мало изучить условные и циклические конструкции. Необходимо использовать еще и другие возможности языка C#. В этом видео уроке Вы рассмотрите важный элемент любого приложения — метод.

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

Главная задача для бoльшинствa приложeний cводится к aнализу дaнных и вычиcлений, производимых нaд бoльшими объeмами дaнных. Для тoго, чтoбы в Вaших прилoжениях пoявилась возможноcть хрaнить многo однoтипных знaчений, Вaм нужно cоздавать маcсив. Видeо урок обучит Вaс нe тoлько кaк сoздать мaссивы, но и произвoдить oсновные опeрации нaд ними. Вы узнаeте, какиe нужно использовaть циклы при рaботе с мaссивами и изучите такие типы массивов как одномерные, многомерные, зубчатые.

Практикум курса C# Стартовый на примерах из GTA 5

Роман Самчук подготовил новый необычный курс для желающих выучить C#. Чтобы знания усваивались необходима практика, а что может быть интереснее и практичнее, чем мод для GTA V? Именно на его примере мы разберем все основные синтаксические конструкции языка и их особенности употребления. Видео будет крайне полезно новичкам в программировании.

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

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

Встречайте новый урок от Романа Самчука. На предыдущих занятиях мы рассмотрели с вами, как создавать статические вещи в игре. В этот раз, мы добавим в игру невероятно удобного персонажа — киллера. Научим его ходить, выбирать цель и делать за нас работу — убивать персонажей в игре.

В этом уроке будем работать с native function, которые есть в игре. Будет показано, как их можно вызывать, как передавать им различные параметры. Также, создадим машину с четырьмя врагами и попробуем из перестрелять.

Видео курс C# Базовый.


На первом уроке видео курса C# Essential будет рассмотрено: Концепции объектно-ориентированного программирования (ООП). Классы в языке C#. Основы работы с конструктором. Назначение и использование свойств (get, set). Главных парадигмы ООП.

В видео уроке «Классы и объекты. Диаграммы классов» будет продолжена тема урока «Введение в OOП. Классы и объекты», а также будет раскрыта тема возможности языка программирования C# разделять определение класcа между двумя и/или более файлами, именуемая частичными или partial классами. После ознакомления с частичными классами в С#, будут рассмотрены диаграммы классов, связи отношений между классами такие как ассоциация, агрегация, композиция, реализация, самоассоциация зависимости и другие.

В ходе видео урока Вам будет представлена информация о двух основных парадигмах ООП — полиморфизме и наследовании, также Вы познакомитесь с модификаторами доступа и виртуальными членами.

В видео уроке будет продемонстрированы практические примеры создания и использования абстрактных классов и интерфейсов. Абстракция позволяет программисту рассматривать объект, не разбирая сумму сложных частей, из которых состоит данный объект. В ходе видео урока Вы получите необходимые знания, которые помогут Вам разобраться с понятием абстракции в С#.

В видео уроке рассмотрены примеры создания и практического применения массивов и индексаторов. Тренер объясняет Вам принципы создания и практического применения индексаторов и способы их переопределения. Программисты с опытом знакомы с процессом обращения к индивидуальным элементам, которые содержат стандартные массивы.

В видео уроке будут рассмотрены статические классы, принципы создания и практического применения статических членов. Также, в ходе видео урока будет объяснена работа и использование расширяющих методов.Во второй части видео урока тренер рассмотрит понятие вложенных классов и шаблон проектирования «Одиночка» (Singleton).

Описание: В видео уроке будет представлена полная информация о структурах, рассмотрены отличия между классами и структурами, а также рассказаны практические советы по их применению. Структуры — фундаментальные типы данных в языке программирования C#.

В данном видео уроке будут рассмотрены такие понятия как упаковка (boxing) и распаковка (unboxing), структурный тип DateTime, а также работа с перечислениями(enum). В ходе занятия тренер ознакомит студентов с практическими примерами, которые позволят с легкостью использовать и применять полученные на уроке знания.

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

В языке программирования C# существует два механизма для создания кода, который будет повторно использован через различные типы — уже рассмотренное ранее наследование и обобщения. Обобщения, в отличии от наследования, выражают повторное использование кода через использование универсальных шаблонов(generics), в которых применяются различные типы данных на этапе выполнения.

Описание: В видео уроке «Ограничения универсальных шаблонов» Вас ждет продолжение знакомства с универсальными шаблонами в C#. Вы узнаете, каким образом можно использовать ограничения для обобщенных типов данных. В ходе видео урока тренер остановит Ваше внимание на работе с Nullable типами, а также операциях поглощения, показав примеры практического их использования.

Весь видео урок будет всецело посвящен работе с событиями в C#. В деталях будет рассмотрено, каким образом создавать «издателей» и «подписчиков», а также обращаться к созданным событиям и вызывать их. Тренер уделит отдельное внимание делегату EventHandler и базовому классу EventArgs, а также работе с ними

В процессе просмотра видео урока Вы получите основные сведения, которые потребуются Вам для работы с многопоточностью в языке программирования C#. Многопоточность — важное средство многозадачного программирования среды .NET. Видео урок даст Вам основное понимание многопоточности в языке программирования С#. Также в ходе урока тренер расскажет Вам об использовании делегатов ThreadStart и ParameterizedThreadStart и объяснит работу с критическими секциями, как средствами синхронизации доступа потоков к различным разделяемым ресурсам.

В видео уроке будут объяснены коллекции, их назначение и примеры их практического применения. Также Вы детально изучите базовые интерфейсы IEnumerable, IEnumerator. Также в ходе видео урока тренер рассмотрит с Вами примеры создания и использования пользовательских коллекций, продемонстрирует наглядные примеры по работе оператора yield.

В этом видео уроке Вы узнаете какие системные исключения существуют в языке C# и как правильно обрабатывать исключительные ситуации с помощью конструкции try — catch — finally. Также вы научитесь создавать свои объекты исключения. При выполнение приложения может сложится ситуация, когда корректное выполнение приложения невозможно. Например, приложение читает файл на диске, которого нет.

В данном видео уроке тренером будет рассмотрен базовый класс object его применение и использование, а так же техника перегрузки операторов. В процессе объяснения будет затронута техника клонирования, а также будет рассмотрено назначение шаблона проектирования «Прототип» (Prototype) и интерфейса ICloneable.

Данный видео урок будет очень важен для понимания всех современных технологий. В ходе видео урока тренер познакомит Вас с основами LINQ, а также с анонимными и динамическими типами, которые активно используются при построении запросов. Язык LINQ — это набор функций, который позволяет значительно расширить возможности синтаксиса языка программирования C#.

В этом видеоуроке Вы узнаете, что такое пространства имен и как правильно организовывать проект используя пространства имен. Также Вы узнаете, как создавать библиотеки (DLL) в языке C#. Тренер рассмотрит тип проекта Class Library и на простом примере объяснить для чего используются библиотеки.

C# — C# — Возможно ли(как) использовать «

Контекст синхронизации (SynchronizationContext) — позволяет возобновить выполнение метода в конкретном потоке.
Текущий контекст SynchronizationContext – это свойство текущего потока. Идея в том, что всякий раз, как код исполняется в каком-то специальном потоке, мы
можем получить текущий контекст синхронизации и сохранить его.
Впоследствии этот контекст можно использовать для того, чтобы продолжить исполнение кода в том потоке, в котором оно было начато.

В классе SynchronizationContext есть важный метод Post , который гарантирует, что переданный делегат будет исполняться в правильном контексте.

Важно! В момент приостановки метода при встрече оператора await текущий контекст SynchronizationContext сохраняется.
Далее, когда метод возобновляется, компилятор вставляет вызов Post , чтобы исполнение возобновилось в запомненном контексте.

Код называется асинхронным , если он запускает какую-то длительную операцию, но не дожидается ее завершения. Противоположностью является блокирующий код, который ничего не делает, пока операция не завершится.

К числу таких длительных операций можно отнести:
• сетевые запросы;
• доступ к диску;
• продолжительные задержки.

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

Всякий раз, запуская новый поток или пользуясь классом ThreadPool, вы пишете асинхронную программу , потому что текущий поток может продолжать заниматься другими вещами.

Асинхронный код освобождает поток , из которого был запущен. И это очень хорошо по многим причинам.

Часто существует лишь один поток, способный выполнить определенную задачу (например, поток пользовательского интерфейса) и, если не освободить его быстро, то приложение перестанет реагировать на действия пользователя.

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

В версии C# 5.0 Microsoft добавила механизм, предстающий в виде двух новых ключевых слов: async и await .

Механизм async встроен в компилятор и без поддержки с его стороны не мог бы быть реализован в библиотеке. Компилятор преобразовывает исходный код, то есть действует примерно по тому же принципу, что в случае лямбда-выражений и итераторов в предыдущих версиях C#.

У ASP.NET-приложений на веб-сервере нет ограничения на единственный поток , как в случае программ с пользовательским интерфейсом. И тем не менее асинхронное выполнение может оказаться весьма полезным, так как для таких приложений характерны длительные операции, особенно запросы к базе данных.

В зависимости от версии IIS может быть ограничено либо общее число потоков, обслуживающих веб-запросы, либо общее число одновременно обрабатываемых запросов. Если большая часть времени обработки запроса уходит на обращение к базе данных, то увеличение числа одновременно обрабатываемых запросов может повысить пропускную способность сервера

Помните: основная отличительная особенность асинхронного кода состоит в том, что поток, начавший длительную операцию, освобождается для других дел .
В случае ASP.NET этот поток берется из пула потоков, поэтому после запуска длительной операции он сразу же возвращается в пул и может затем использоваться для обработки других запросов. Таким образом, для обработки одного и того же количества запросов требуется меньше потоков.

Библиотека Task Parallel Library была включена в версию .NET Framework 4.0. Важнейшим в ней является класс Task , представляющий выполняемую операцию. Его универсальный вариант, Task , играет роль обещания вернуть значение (типа T), когда в будущем, по завершении операции, оно станет доступно.

Как мы увидим ниже, механизм async в C# 5.0 активно пользуется классом Task. Но и без async классом Task, а особенно его вариантом Task , можно воспользоваться при написании асинхронных программ. Для этого нужно запустить операцию, которая возвращает Task , а затем вызвать метод ContinueWith для регистрации обратного вызова.
Это называется асинхронность вручную потому что используем метод ContinueWith .

Заметка на будующее:
Класс Task умеет в частности обрабатывать исключения и работать с контекстами синхронизации SynchronizationContext .
Мы увидим, что это полезно, когда требуется выполнить обратный вызов в конкретном потоке (например, в потоке пользовательского интерфейса).

1-ый шаг это пометка метода ключевым словом async .
Оно включается в сигнатуру метода точно так же, как, например, слово static.

2-ой шаг мы должны дождаться завершения скачивания, воспользовавшись ключевым словом await .
С точки зрения синтаксиса C#, await – это унарный оператор, такой же как оператор ! или оператор приведения типа (type).
Он располагается слева от выражения и означает, что нужно дождаться завершения асинхронного выполнения этого выражения.

На заметку!
Метод, помеченный ключевым словом async, автоматически не становится асинхронным .
Async-методы лишь упрощают использование других асинхронных методов. Они начинают исполняться синхронно, и так происходит до тех пор, пока не встретится вызов асинхронного метода внутри оператора await .
В этот момент сам вызывающий метод становится асинхронным.
Если же оператор await не встретится, то метод так и будет выполняться синхронно до своего завершения.

Я говорил, что класс Task представляет выполняемую операцию, а его подкласс Task – операцию, которая в будущем вернет значение типа T. Можно считать, что Task – это обещание вернуть значение типа T по завершении длительной операции.
Оба класса Task и Task могут представлять асинхронные операции, и оба умеют вызывать ваш код по завершении операции. Чтобы воспользоваться этой возможностью вручную, необходимо вызвать метод ContinueWith , передав ему код, который должен быть выполнен, когда длительная операция завершится. Именно так и поступает оператор await , чтобы выполнить оставшуюся часть async-метода .

Если применить await к объекту типа Task , то мы получим выражение await , которое само имеет тип T .
Это означает, что результат оператора await можно присвоить переменной, которая используется далее в методе, что мы и видели в примерах.
Однако если await применяется к объекту неуниверсального класса Task , то получается предложение await , которое ничему нельзя присвоить (как и результат метода типа vo >Task не обещает вернуть значение в качестве результата, а представляет лишь саму операцию.

Метод, помеченный ключевым словом async , может возвращать значения трех типов:
• void
• Task
• Task , где T – некоторый тип.

Никакие другие типы не допускаются, потому что в общем случае
исполнение асинхронного метода не завершается в момент возврата управления. Как правило, асинхронный метод ждет завершения длительной операции, то есть управление-то он возвращает сразу, но результат в этот момент еще не получен и, стало быть, недоступен вызывающей программе.

Я провожу различие между типом возвращаемого методом значения (например, Task ) и типом результата, который нужен вызывающей программе (в данном случае string).
В обычных, не асинхронных, методах тип возвращаемого значения совпадает с типом результата, тогда как в асинхронных методах они различны – и это очень существенно.

Использовать тип void следует, когда вы уверены, что вызывающей программе безразлично, когда завершится операция и завершится ли она успешно.

Async-методы , возвращающие тип Task , позволяют вызывающей программе ждать завершения асинхронной операции и распространяют исключения, имевшие место при ее выполнении.

Если значение результата несущественно, то метод типа async Task предпочтительнее метода типа async void , потому что вызывающая программа получает возможность узнать о завершении операции, что упрощает упорядочение задач и обработку исключений.

Ключевое слово async указывается в объявлении метода, как public или static.
Однако спецификатор async не считается частью сигнатуры метода, когда речь заходит о переопределении виртуальных методов, реализации интерфейса или вызове.
То есть в отношении переопределения методов и реализации интерфейсов, слово async полностью игнорируется.

В объявлениях методов интерфейса слово async запрещено просто потому, что в этом нет необходимости. Если в интерфейсе объявлен метод, возвращающий тип Task , то в реализующем интерфейс классе этот метод может быть помечен словом async, а может быть и не помечен – на усмотрение программиста.

Когда поток исполнения программы доходит до оператора await, должны произойти две вещи:

1) Текущий поток должен быть освобожден, чтобы поведение программы было асинхронным. С привычной, синхронной, точки зрения это означает, что метод должен вернуть управление.

2) Когда задача Task , погруженная в оператор await, завершится, ваш метод должен продолжить выполнение с того места, где перед этим вернул управление, как будто этого возврата никогда не было.

Чтобы добиться такого поведения, метод должен приостановить выполнение, дойдя до await , и возобновить его впоследствии.

Чтобы стало яснее, сколько работы должен выполнить компилятор C#, встретив в программе оператор await , я перечислю, какие именно аспекты состояния метода необходимо сохранить.

Во-первых, запоминаются все локальные переменные метода, в том числе:
• параметры метода
• все переменные, определенные в текущей области видимости
• все прочие переменные, например счетчики циклов
• переменную this, если метод не статический

В результате после возобновления метода окажутся доступны все переменные-члены класса.
Всё это сохраняется в виде объекта в куче .NET, обслуживаемой сборщиком мусора. Таким образом, встретив await , компилятор выделяет память для объекта, то есть расходует ресурсы, но в большинстве случае это не приводит к потере производительности.
C# также запоминает место, где встретился оператор await .

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

ExecutionContext — это родительский контекст, включающий все остальные контексты. Он не имеет собственного поведения, а служит только для запоминания и передачи контекста и используется такими компонентами .NET, как класс Task.

Оператор await можно использовать почти в любом месте метода, помеченного ключевым словом async .

Оператор await может встречаться внутри блока try , но не внутри блоков catch или finally .

В блоке catch часто, а в блоке finally всегда, исключение еще находится в фазе раскрутки стека и позже может быть возбуждено в блоке повторно.

Если использовать в этой точке await, то стек окажется другим, и определить в этой ситуации поведение повторного возбуждения исключения было бы очень сложно.
Напомню, что await всегда можно поставить не внутри блока catch, а после него, для чего следует либо воспользоваться предложением return, либо завести булевскую переменную, в которой запомнить, возбуждала ли исходная операция исключение. Например, вместо такого некорректного в C# кода:

можно было бы написать:

bool failed = false;

try
<
page = await webClient.DownloadStringTaskAsync( «http://aaa.com» );
>
catch (WebException)
<
failed = true;
>

if (failed)
<
page = await webClient.DownloadStringTaskAsync( «http://ooo.com» );
>

По завершении операции в объекте Task сохраняется информация о том, завершилась ли она успешно или с ошибкой. Получить к ней доступ проще всего с помощью свойства IsFaulted, которое равно true, если во время выполнения операции произошло исключение.
Оператор await знает об этом и повторно возбуждает исключение, хранящееся в Task.
У читателя, знакомого с системой исключений в .NET, может возникнуть вопрос, корректно ли сохраняется первоначальная трассировка стека исключения при его повторном возбуждении.

Раньше это было невозможно; каждое исключение могло быть возбуждено только один раз. Однако в .NET 4.5 это ограничение снято благодаря новому классу ExceptionDispatchInfo, который взаимодействует с классом Exception с целью запоминания трассировки стека и воспроизведения ее при повторном возбуждении.

Async-методы также знают об исключениях. Любое исключение, возбужденное, но не перехваченное в async-методе, помещается в объект Task, возвращаемый вызывающей программе. Если в этот момент вызывающая программа уже ждет объекта Task, то исключение будет возбуждено в точке ожидания. Таким образом, исключение передается вызывающей программе вместе со сформированной виртуальной трассировкой стека – точно так же, как в синхронном коде.

Я называю это виртуальной трассировкой стека, потому что стек – вообще-то принадлежность потока, а в асинхронной программе реальный стек текущего потока может не иметь ничего общего с трассировкой стека в момент исключения. В исключении запоминается трассировка стека, отражающая намерение программиста, в ней представлены те методы, который программист вызывал сам, а не детали того, как C# исполнял части этих методов в действительности.

Асинхронные методы до поры исполняются синхронно Выше я уже отмечал, что async-метод становится асинхронным , только встретив вызов асинхронного метода внутри оператора await . До этого момента он работает в том потоке, в котором вызван, как обычный синхронный метод.

Иногда длительная операция не отправляет запросов в сеть и не обращается к диску, а просто выполняет продолжительное вычисление.
Разумеется, нельзя рассчитывать на то, что поток быстро выполнит, желательно избежать зависания пользовательского интерфейса.
Для этого мы должны вернуть управление потоку пользовательского интерфейса, чтобы он мог обрабатывать другие события, а длительное вычисление производить в отдельном потоке.

Класс Task позволяет это сделать, а для обновления пользовательского интерфейса по завершении вычисления мы можем, как обычно, использовать await:

Метод Task.Run исполняет переданный ему делегат в потоке, взятом из пула ThreadPool. В данном случае я воспользовался лямбда-выражением, чтобы упростить передачу счетной задаче локальных переменных. Возвращенная задача Task запускается немедленно, и мы можем дождаться ее завершения, как любой другой задачи:

Это очень простой способ выполнить некоторую работу в фоновом потоке.
Если необходим более точный контроль над тем, какой поток производит вычисления или как он планируется, в классе Task имеется статическое свойство Factory типа TaskFactory. У него есть метод StartNew с различными перегруженными вариантами для управления вычислением:

В разделе «Task и await» выше мы видели, как просто организовать выполнение несколько параллельных асинхронных задач, – нужно запустить их по очереди, а затем ждать завершения каждой. Потом мы узнаем, что необходимо дождаться завершения каждой задачи,
иначе можно пропустить исключения.

Для решения этой задачи можно воспользоваться методом Task.
WhenAll , который принимает несколько объектов Task и порождает агрегированную задачу, которая завершается, когда завершены все исходные задачи. Вот простейший вариант метода WhenAll (имеется также перегруженный вариант для коллекции универсальных объектов Task ):

Task WhenAll (IEnumerable tasks)

Основное различие между использованием WhenAll и самостоятельным ожиданием нескольких задач, заключается в том, что WhenAll корректно работает даже в случае исключений. Поэтому старайтесь всегда пользоваться методом WhenAll .

Универсальный вариант WhenAll возвращает массив, содержащий
результаты отдельных поданных на вход задач Task. Это сделано скорее для удобства, чем по необходимости, потому что доступ к исходным объектам Task сохраняется, и ничто не мешает опросить их свойство Result , так как точно известно, что все задачи уже завершены.

Часто возникает также ситуация, когда требуется дождаться завершения первой задачи из нескольких запущенных. Например, так бывает, когда вы запрашиваете один и тот же ресурс из нескольких источников и готовы удовлетвориться первым полученным ответом.

Task > WhenAny (IEnumerable > tasks)

В ситуации, когда возможны исключения, пользоваться методом WhenAny следует с осторожностью. Если вы хотите знать обо всех исключениях, произошедших в программе,
то необходимо ждать каждую задачу, иначе некоторые исключения могут быть потеряны. Воспользоваться методом WhenAny и просто забыть об остальных задачах – всё равно, что перехватить все исключения и игнорировать их.
Это достойное порицания решение, которое может впоследствии привести к тонким ошибкам и недопустимым состояниям программы.

Метод WhenAny возвращает значение типа Task >. Это означает, что по завершении задачи вы получаете объект типа Task .
Он представляет первую из завершившихся задач и поэтому гарантированно находится в состоянии «завершен». Но почему нам возвращают объект Task, а не просто значение типа T?

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

Task > anyTask = Task.WhenAny(tasks);
Task winner = await anyTask;
Image image = await winner; // Этот оператор всегда завершается синхронно

AddAFavicon(image);
foreach (Task eachTask in tasks)
<
if (eachTask != winner)
<
await eachTask;
>
>

Нет ничего предосудительного в том, чтобы обновить пользовательский интерфейс, как только будет получен результат первой завершившейся задачи (winner), но после этого необходимо дождаться остальных задач, как сделано в коде выше. При удачном стечении обстоятельств все они завершатся успешно, и на выполнении программы это никак не отразится. Если же какая-то задача завершится с ошибкой, то вы сможете узнать причину и исправить ошибку.

Отмена асинхронных операций связывается с типом CancellationToken

По соглашению всякий метод, поддерживающий отмену, должен иметь перегруженный вариант, в котором за обычными параметрами следует параметр типа CancellationToken

При вызове метода ThrowIfCancellationRequested отмененного объекта CancellationToken возбуждается исключение типа OperationCanceledException.

Библиотека Task Parallel Library знает, что такое исключение представляет отмену, а не ошибку, и обрабатывает его соответственно. Например, в классе Task имеется свойство IsCanceled , которое автоматически принимает значение true, если при выполнении async-метода произошло исключение OperationCanceledException.

Удобной особенностью подхода к отмене, основанного на маркерах CancellationToken , является тот факт, что один и тот же маркер можно распространить на столько частей асинхронной операции, сколько необходимо, – достаточно просто передать его всем частям.
Неважно, работают они параллельно или последовательно, идет ли речь о медленном вычислении или удаленной операции, – один маркер отменяет всё.

До первого await не происходит ничего интересного.
Async не планирует выполнение метода в фоновом потоке. Единственный способ сделать это – воспользоваться методом Task.Run , который специально предназначен для этой цели, или чем-то подобным.

В приложении с пользовательским интерфейсом это означает, что код до первого await работает в потоке пользовательского интерфейса.
А в веб-приложении на базе ASP.NET – в рабочем потоке ASP.NET.

Часто бывает, что выражение в строке, содержащей первый await, содержит еще один async-метод .
Поскольку это выражение предшествует первому await, оно также выполняется в вызывающем потоке.
Таким образом, вызывающий поток продолжает «углубляться» в код приложения, пока не встретит метод, действительно возвращающий объект Task .

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

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

Текущий контекст SynchronizationContext – это свойство текущего потока. Идея в том, что всякий раз, как код исполняется в каком-то специальном потоке, мы можем получить текущий контекст синхронизации и сохранить его.
Впоследствии этот контекст можно использовать для того, чтобы продолжить исполнение кода в том потоке, в котором оно было начато.

Поэтому нам не нужно точно знать, в каком потоке началось исполнение, достаточно иметь соответствующий объект SynchronizationContext .

В классе SynchronizationContext есть важный метод Post , который гарантирует, что переданный делегат будет исполняться в правильном контексте.

Мы знаем, что код, предшествующий первому await , исполняется в вызывающем потоке, но что происходит, когда исполнение вашего метода возобновляется после await ?

На самом деле, в большинстве случаев он также исполняется в вызывающем потоке, несмотря на то, что в промежутке вызывающий поток мог делать что-то еще.
Это существенно упрощает написание кода.

Для достижения такого эффекта используется класс SynchronizationContext .

Важно.
В момент приостановки метода при встрече оператора await текущий контекст SynchronizationContext сохраняется.
Далее, когда метод возобновляется, компилятор вставляет вызов Post , чтобы исполнение возобновилось в запомненном контексте.

В классе Task имеется свойство Result , обращение к которому блокирует вызывающий поток до завершения задачи.

Его можно использовать в тех же местах, что await , но при этом не требуется, чтобы метод был помечен ключевым словом async или возвращал объект Task .
И в этом случае один поток занимается – на этот раз вызывающий (то есть тот, что блокируется).

Большинство написанных вами async-методов возвращают значение типа Task или Task . Цепочка таких методов, в которой каждый ожидает завершения следующего, представляет собой асинхронный аналог стека вызовов в синхронном коде.

Компилятор C# прилагает максимум усилий к тому, чтобы исключения, возбуждаемые в этих методах, вели себя так же, как в синхронном случае. В частности, блок try-catch , окружающий ожидаемый async-метод, перехватывает исключения, возникшие внутри этого метода

async Task Catcher()
<
try
<
await Thrower();
>
catch (AlexsException)
<
// Исключение будет обработано здесь
>
>

async Task Thrower()
<
await Task.Delay(100);
throw new AlexsException();
>

Для этого C# перехватывает все исключения, возникшие в вашем async-методе . Перехваченное исключение помещается в объект Task , который был возвращен вызывающей программе. Объект Task переходит в состояние Faulted. Если задача завершилась с ошибкой, то ожидающий ее метод не возобновится как обычно, а получит исключение, возбужденное в коде внутри await .

В async-методе оно возбуждается там, где находится оператор await , а не в точке фактического вызова метода. Это становится очевидным, если разделить вызов и await .

Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих