Bài 6: Hỗ trợ xử lý đồng thời (Concurrency)

Hỗ trợ để xử lý nhiều việc cùng lúc là nền tảng cho mọi khối lượng công việc trên thực tế, cho dù đó là ứng dụng client đang xử lý nền trong khi vẫn giữ cho giao diện người dùng phản hồi nhanh, các dịch vụ xử lý hàng ngàn yêu cầu đồng thời, các thiết bị phản hồi vô số tác nhân kích hoạt đồng thời hoặc các máy tính cao cấp hỗ trợ song song việc xử lý các hoạt động sử dụng nhiều sức mạnh tính toán. Các hệ điều hành cung cấp hỗ trợ cho sự đồng thời như vậy thông qua các luồng (thread), cho phép nhiều dòng mã lệnh được xử lý độc lập, với hệ điều hành quản lý việc thực thi các luồng đó trên bất kỳ lõi bộ xử lý có sẵn nào trong máy. Các hệ điều hành cũng cung cấp hỗ trợ để thực hiện I/O, với các cơ chế được cung cấp để cho phép I/O được thực hiện theo cách có thể mở rộng với nhiều thao tác I/O “đang hoạt động” tại bất kỳ thời điểm cụ thể nào. Sau đó, các ngôn ngữ lập trình và các framework có thể cung cấp nhiều mức độ trừu tượng khác nhau trên nền tảng hỗ trợ cốt lõi này.

.NET cung cấp hỗ trợ đồng thời và song song hóa như vậy ở nhiều cấp độ trừu tượng, cả thông qua các thư viện và được tích hợp sâu vào C#. Một class Thread nằm ở cuối hệ thống phân cấp và đại diện cho một thread của hệ điều hành, cho phép các nhà phát triển tạo các thread mới và sau đó tham gia với chúng. ThreadPool nằm phía trên đầu các thread, cho phép các nhà phát triển suy nghĩ về các hạng mục công việc được lên lịch không đồng bộ để chạy trên một nhóm thread, với việc quản lý các thread đó (bao gồm cả việc thêm và xóa các thread khỏi nhóm cũng như chỉ định các đầu mục công việc cho các chủ đề đó) còn lại trong thời gian chạy. Task sau đó cung cấp một cách biểu diễn thống nhất cho bất kỳ hoạt động nào được thực hiện không đồng bộ và có thể được tạo và kết hợp theo nhiều cách; ví dụ: Task.Run cho phép lập lịch trình một nhiệm vụ chạy trên ThreadPool và trả về một Task để thể hiện kết quả cuối cùng của công việc đó, trong khi đó Socket.ReceiveAsync trả về một Task (hoặc ValueTask) thể hiện kết quả cuối cùng hoàn thành I/O không đồng bộ để đọc dữ liệu đang chờ xử lý hoặc trong tương lai từ Socket. Một loạt các synchronization primitive được cung cấp để điều phối các hoạt động đồng bộ và không đồng bộ giữa các thread và hoạt động không đồng bộ và vô số API cấp cao hơn được cung cấp để dễ dàng triển khai các mẫu xử lý đồng thời phổ biến, ví dụ: Parallel.ForEach và Parallel.ForEachAsync giúp dễ dàng xử lý song song tất cả các thành phần của một chuỗi dữ liệu.

Hỗ trợ lập trình không đồng bộ cũng là một tính năng first-class của ngôn ngữ lập trình C#, cung cấp các từ khóa async và await giúp dễ dàng viết và soạn thảo các hoạt động không đồng bộ trong khi vẫn tận hưởng đầy đủ lợi ích của tất cả cấu trúc luồng điều khiển mà ngôn ngữ này cung cấp .

Bài 5: Xử lý lỗi trong .NET

Exception (ngoại lệ) là mô hình xử lý lỗi chính trong .NET. Các exception có ưu điểm là thông tin lỗi không cần phải được khai báo trong method signature hoặc phải được trả về trong các phương thức.

Đoạn code sau mô tả một cách sử dụng tiêu biểu:

try
{
    var lines = await File.ReadAllLinesAsync(file);
    Console.WriteLine($"The {file} has {lines.Length} lines.");
}
catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException)
{
    Console.WriteLine($"{file} doesn't exist.");
}

Xử lý các exception một cách đúng đắn là điều cần thiết cho độ tin cậy của ứng dụng. Các exception dự kiến ​​có thể được xử lý có chủ ý trong code của người dùng, ngược tại, ứng dụng sẽ bị crash. Một ứng dụng bị crash vẫn đáng tin cậy hơn một ứng dụng có hành vi không xác định. Nó cũng dễ chẩn đoán hơn khi bạn muốn tìm ra nguyên nhân cốt lõi của vấn đề.

Continue reading “Bài 5: Xử lý lỗi trong .NET”

Bài 4: Tính an toàn của .NET

An toàn là một trong những chủ đề quan trọng nhất trong thập kỷ vừa qua. Nó cũng là một thành phần cốt yếu đối với một môi trường được quản lý như .NET.

Các dạng an toàn:

  • An toàn kiểu (type safety) — Một kiểu không thể được dùng ở chỗ của một kiểu khác, nhằm tránh các hành vi không mong muốn.
  • An toàn bộ nhớ (memory safety)— Chỉ bộ nhớ được cấp phát mới được sử dụng, ví dụ: một biến chỉ được tham chiếu đến một đối tượng hợp lệ hoặc là null.
  • Concurrency or thread safety — Dữ liệu được chia sẻ không được truy cập theo cách có thể dẫn đến hành vi không xác định.

Ghi chú: Chính quyền liên bang Mỹ gần đây đã phát hành một hướng dẫn về Sự quan trọng của anh toàn bộ nhớ.

.NET được thiết kế như một nền tảng an toàn ngay từ đầu. Cụ thể, nó nhằm mục đích tạo ra một thế hệ máy chủ web mới vốn cần chấp nhận đầu vào không đáng tin cậy trong môi trường máy tính thù địch (dịch từ chữ hostile) nhất thế giới (Internet). Hiện tại, người ta thường đồng ý rằng các chương trình web nên được viết bằng một ngôn ngữ an toàn.

Continue reading “Bài 4: Tính an toàn của .NET”

Bài 3: Quản lý bộ nhớ tự động

.NET runtime (trình thực thi các ứng dụng .NET) cung cấp khả năng quản lý bộ nhớ tự động thông qua bộ dọn rác (garbage collector – GC). Với bất kỳ ngôn ngữ nào, mô hình quản lý bộ nhớ luôn là một trong những đặc tính quan trọng nhất. Điều này cũng đúng với .NET.

Các lỗi liên quan đến bộ nhớ heap (bộ nhớ nơi chúng ta xin cấp phát và giải phóng – https://daohainam.com/2021/08/14/bo-nho-heap-la-gi/) thường rất khó để debug. Không có gì lạ khi thấy các kỹ sư phải mất hàng tuần, thậm chí hàng tháng trời để có thể dò ra chúng. Nhiều ngôn ngữ dùng một bộ dọn rác (GC) như một cách thân thiện để loại bỏ các bug đó vì GC sẽ đảm bảo quản lý vòng đời cái đối tượng một cách chính xác. Thường thì GC sẽ giải phóng bộ nhớ hàng loạt để hiệu quả hơn. Việc tạm dừng để dọn rác có thể sẽ không phù hợp với các chương trình có yêu cầu rất cao về độ trễ, và bản thân việc sử dụng bộ nhớ có thể sẽ cao hơn. GC có memory locality (1) tốt hơn và một số có khả năng dồn các vùng nhớ giúp nó ít bị phân mảnh (2) hơn.

.NET có một bộ GC có khả năng tự điều chỉnh, hoạt động theo kiểu tracing (3). Nó nhằm mục đích mang lại khả năng vận hành “không cần thao tác” trong những trường hợp thông thường, đồng thời cung cấp các tùy chọn cấu hình với trường hợp khối lượng công việc lớn. GC là kết quả của nhiều năm đầu tư, cải tiến và học hỏi từ nhiều loại khối lượng công việc.

Sơ đồ bộ nhớ .NET
Continue reading “Bài 3: Quản lý bộ nhớ tự động”

Bài 2: Hệ thống kiểu trong .NET

.NET cung cấp một hệ thống kiểu rất rộng, phục vụ gần như ngang nhau cho safety (tính an toàn), descriptiveness (khả năng tự mô tả), dynamism (kiểu động), and native interop (tương tác với các thành phần native).

Trước hết, hệ thống kiểu cho phép một mô hình lập trình hướng đối tượng. Nó bao gồm các kiểu, kế thừa (đơn thừa kế), interface (bao gồm default method implementation (phương thức mặc định)) và virtual method để cung cấp một hành vi phù hợp cho tất cả các phân lớp kiểu mà hướng đối tượng cho phép.

Generics là một tính năng phổ biến cho phép các lớp chuyên biệt hóa thành một hoặc nhiều kiểu. Ví dụ: List<T> là một lớp chung mở, nhờ đó ta có thể viết List<string> và List<int> ta không cần phải tạo thêm các lớp ListOfString và ListOfInt riêng biệt, hoặc phải dựa vào object và truyền như trường hợp của ArrayList. Generics cũng cho phép tạo nhiều hệ thống hữu ích trên các kiểu khác nhau (và giảm nhu cầu sử dụng nhiều code), như với Generic Math.

Continue reading “Bài 2: Hệ thống kiểu trong .NET”

.NET là gì? Và vì sao ta nên chọn nó? – Bài 1

Đây là bài viết được dịch từ https://devblogs.microsoft.com/dotnet/why-dotnet/

Bài viết này nhằm giới thiệu về .NET, một trong những nền tảng lập trình phổ biến nhất, mạnh mẽ nhất, hỗ trợ đầy đủ tất cả các hệ điều hành, kiến trúc, từ smart phone, đến desktop, máy chủ… Với sự hậu thuẫn mạnh mẽ bởi Microsoft và cộng đồng hàng triệu lập trình viên trên khắp thế giới.

Hiểu rõ những khả năng của .NET giúp bạn quyết định việc đầu tư việc học vào nền tảng này.

Phần chữ in nghiêng là phần ghi chú thêm của người dịch.

.NET đã thay đổi rất nhiều từ lúc chúng tôi khởi động dự án chuyển đổi .NET sang mã mở và đa nền tảng. Chúng tôi đã khảo sát lại toàn bộ, tinh chỉnh lại nền tảng, thêm vào các tính năng ở cấp thấp hỗ trợ cho hiệu năng và tính an toàn, cùng với các tính năng ở cấp cao giúp việc xây dựng ứng dụng nhanh chóng và hiệu quả.  Span<T>hardware intrinsics, và nullable reference types là một số ví dụ. Chúng tôi cũng khởi động một blog “.NET Design Point” để giúp bạn biết thêm về những khái niệm và quyết định khi thiết kế ra .NET platform mà chúng ta có ngày nay, và nó đã giúp ích như thế nào khi viết code.

Bài đầu tiên của loại bài này cung cấp một cái nhìn tổng quan về các trụ cột chính và các design point của nền tảng. Nó mô tả ở mức độ khái quát “những gì bạn nhận được” khi bạn chọn .NET và nhằm mục đích trở thành một bộ khung tập trung vào những điểm quan trọng mà bạn có thể dựa trên đó để giới thiệu nền tảng này đến những người khác. Các bài viết tiếp theo sẽ đi vào chi tiết của các chủ đề đã nói đến. Bài viết này sẽ không mô tả về các công cụ, như Visual Studio, hoặc các thư viện cấp cao hoặc các mô hình ứng dụng như ASP.NET Core.

“.NET” mà chúng ta đang nói đến là .NET Core hiện đại. Nếu bạn quên, thì chúng tôi đã bắt đầu dự án này vào năm 2014 như một dự án nguồn mở trên GitHub. Nó có thể chạy Linux, macOS, và Windows on Arm64, x64, và các kiến trúc chíp khác nữa. Nó cũng có sẵn trong nhiều Linux distro. Nó cũng duy trì rất nhiều sự tương thích với .NET Framwork cũ, nhưng nó thực sự là một sản phẩm và là một hướng đi mới.

Continue reading “.NET là gì? Và vì sao ta nên chọn nó? – Bài 1”

Thực hiện các tính năng CRUD với Entity Framework trong ứng dụng ASP.NET MVC

Trong bài viết trước, bạn đã tạo một ứng dụng MVC cho phép lưu trữ và hiển thị dữ liệu dùng Entity Framework và SQL Server Compact. Trong phần này bạn sẽ xem lại và tùy biến các câu lệnh cho phép xem, thêm, xóa, sửa dữ liệu mà trình hỗ trợ của MVC đã tự động tạo cho bạn trong các view và controller.

Ghi chú: Trong thực tế, người ta thường dùng mẫu thiết kế Repository để tạo lớp trừu tượng giữa controller và DAL. Để giữ cho bài viết được đơn giản, bạn sẽ không xây dựng một repository cho tới các bài viết sau trong cùng loạt bài này.

(CRUD: Create, Read, Update, Delete)

Continue reading “Thực hiện các tính năng CRUD với Entity Framework trong ứng dụng ASP.NET MVC”

Tạo một mô hình dữ liệu Entity Framework cho ứng dụng ASP.NET MVC

Ứng dụng web của trường đại học Contoso biểu diễn cách tạo ra một ứng dụng ASP.NET MVC dùng Entity Framework. Ứng dụng mẫu là website của trường đại học Contoso (trường đại học này chỉ là hư cấu – không có thật). Nó bao gồm các chức năng như đăng ký nhập học, tạo khóa học, và phân lớp cho giảng viên. Loại bài này sẽ hướng dẫn các bước để xây dựng nên ứng dụng Contoso University. Bạn có thể tải về ứng dụng hoàn chỉnh hoặc tạo mới bằng cách theo các bước hướng dẫn trong bài. Các ví dụ được trình bày bằng C#, mã ứng dụng để có thể tải về được viết bằng C# và VB. Nếu có câu hỏi nào không liên quan trực tiếp đến loại bài này, bạn có thể gửi lên ASP.NET Entity Framework forum hay Entity Framework and LINQ to Entities forum. Chúng tôi sẽ coi như bạn đã biết cách làm việc với ASP.NET MVC trong Visual Studio, nếu chưa, bạn có thể tham khảo basic ASP.NET MVC Tutorial. Nếu bạn định dùng WebForm, xem loại bài Getting Started with the Entity FrameworkContinuing with the Entity Framework. Trước khi bắt đầu, hãy kiểm tra và đảm bảo rằng các thành phần sau đã được cài đặt:

Continue reading “Tạo một mô hình dữ liệu Entity Framework cho ứng dụng ASP.NET MVC”

Từ khóa var

Bắt đầu từ C# 3.0, các biến khai thuộc bên trong các phương thức có thể được khai báo kiểu ngầm định bằng cách dùng từ khóa var. Một biến được khai báo bằng từ khóa này vẫn có kiểu cụ thể, chỉ khác là kiểu này được xác định tự động bởi trình dịch, hai câu khai báo sau là hoàn toàn tương đương:

var i = 10;
int i = 10;

Những ràng buộc sau được áp dụng khi một biến được khai báo bằng var:

  • Biến phải được khởi tạo ngay khi khai báo.
  • Câu lệnh khởi tạo phải là một biểu thức (tức phải có giá trị).
  • Biểu thức khởi tạo phải có một kiểu không phải kiểu null..
  • Câu lệnh khai báo không sử dụng cho việc khai báo nhiều biến đồng thời.
  • Câu lệnh khởi tạo không được phép tham chiếu lại chính biến được khai báo.

Các ví dụ sau là không hợp lệ:

var x; // Lỗi, không có câu lệnh khởi tạo 
var y = {1, 2, 3}; // Lỗi, câu lệnh khởi tạo không được phép là một tập hợp 
var z = null; // Lỗi, không được phép là kiểu null 
var u = x => x + 1; // Lỗi, biểu thức lambda không có kiểu 
var v = v++; // Lỗi, câu lệnh khởi tạo không được phép tham chiếu lại biến đang được khai báo


Chú ý:
Vì lý do tương thích, nếu bạn khai báo một biến bằng từ khóa var, và có một kiểu tên var đang tồn tại (in scope), kiểu var sẽ được dùng thay cho từ khóa var. Tuy nhiên trường hợp này có lẽ ít xảy ra vì tên kiểu var không đúng với quy ước đặt tên kiểu (chữ cái đầu mỗi từ phải là chữ hoa).

Các phát biểu for và using có thể dùng var, và biến được khai báo sẽ có cùng kiểu với biểu thức khởi tạo:

Ví dụ:

for (var i = 1; i <= 10; i++) //Biến i có kiểu int
using (var context = new MyDataContext()) //Biến context có kiểu MyDataContext

Ngược lại, nếu dùng var khi khai báo biến với vòng lặp foreach, biến này sẽ có kiểu của thành phần bên trong tập hợp được duyệt.

Ví dụ:

int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) Console.WriteLine(n); //Biến n có kiểu int

Cũng như biểu thức lambda, bản thân var không phải là một thành phần của LINQ, tuy nhiên nó hỗ trợ LINQ trong việc giúp người dùng khai báo các câu truy vấn một cách đơn giản hơn.

Đào Hải Nam

Extension Methods

Extension method (phương thức mở rộng) cho phép bạn “viết thêm” các phương thức vào một kiểu có sẵn mà không cần tạo các lớp thừa kế, dịch lại hay sửa đổi kiểu dữ liệu sẵn có. Phương thức mở rộng là một dạng đặc biệt của phương thức tĩnh (static), nhưng chúng được gọi giống như là một phương thức thông thường trên kiểu được mở rộng. Với mã lệnh để gọi các phương thức này, khi được viết bằng C# và VB, không có gì khác nhau giữa việc gọi một extension method và các phương thức thực sự được định nghĩa trong kiểu dữ liệu đó.