Blog chia sẽ kinh nghiệm lập trình và Technical VN. Nơi mà các bạn có thể học tập và tham khảo về những công nghệ mới nhất hiện nay về .NET
Thứ Sáu, 20 tháng 3, 2015
Sau bài "Parallel Class Trong C# và Vấn Đề Xử lý Song Song" tôi nhận được khá nhiều câu hỏi về vấn đề này, ví dụ như chúng ta có nên dùng hoàn toàn Parallel.* thay cho cách truyền thống để được lợi về thời gian cũng như đỡ tốn công đồng bộ Data khi dùng Multi Thread. Hôm nay tôi sẽ nói về vấn đề này
I> Parallel và Multi Thread

Chúng ta có thể thấy rằng bản chất của Parallel cũng chỉ là Multi-Thread, song tại sao lại có Parallel khi trước đó đã có Multi Thread:
- Chúng ta được lợi nhiều hơn khi dùng Parallel thay cho Multi Thread bởi việc đồng bộ Data, quản lý Thread, các vấn đề về deadlock... đều đã có người làm việc đó thay mình(Microsoft chịu hoàn toàn trách nhiệm về sản phẩm của họ .. hi)
- Là multi Thread nhưng việc Executeing Thread đó trên hệ thống mà CPU là đa nhân thì làm sao cho hiệu quả cao nhất, Microsoft cũng giải quyết vấn đề này ở tần Framework rồi sau đó "đẻ" ra Parallel hay còn gọi là Multi-Core.
Vậy nên, Parallel xét về mặt tiện lợi và an toàn(phó thác sự tin tưởng) cũng như sẳn dùng cũng như multi-core thì hơn hẳn Multi Thread, xét về việc mà Dev có thể linh hoạt quản lý các Thread và data thì multi Thread đáng dùng hơn.
II> Parallel.* và cách duyệt/lặp truyền thống
Hình trên cho ta thấy cách mà Parallel.* làm việc, nếu mới biết về khái niệm Parallel một số bạn sẽ ưu ái sử dụng nó thay cho cách truyền thống(Parallel.For/ForEach thay cho for/foreach) nhưng chúng ta cần hiểu hơn về cách mà nó làm việc. Ví dụ:
- Dưới đây là một ví dụ mà Pararallel khai thác hết hiệu xuất CPU. Chúng ta có một công việc "my Job", parallel.* sẽ chia công việc của chúng ta ra n phần( tức for chạy từ 1 - n), những phần này không có sự ràng buộc với nhau về dữ liệu trong suốt time xử lý vì thế sẽ không có sự đợi chờ giữa các Thread và các Core. Chúng ta rất lợi về thời gian khi kết quả "my Result" không cần sự tuân thủ về thứ tự các Thread.
Thực tế: Một đám ruộng cần phải gặt song càng sớm càng tốt, chúng ta có 5 người( tức Parallel.For chạy từ 1 - 5) , mỗi người sẽ gặt 1/5 đám ruộng, ai song trước thì nghĩ( là Parallel.For/ForEach) . Y vậy, nếu việc gặt lúa song song cho ta lợi 3t về time và việc chia phần đám ruộng cho 5 người mất 1t thì chúng ta vẫn lợi 2t về thời gian hơn so với việc 5 người lần lược thay nhau gặt ( là for/foreach). => Parallel.* lợi hơn
=> Lưu ý rằng trong trường hợp trên nhưng time bị mất cho việc chia phần công việc lớn hơn nhiều so với tổng chi phí công việc phải làm thì Parallel không phát huy hiệu quả => rất chậm. Chia phần công việc được hiểu là việc hệ thống sẽ tiến hành phân tích và chia Thread chia Core, tính toán đủ thứ.

- Dưới đây là ví dụ về việc dùng Parallel không mang lại hiệu quả. Chúng ta có một công việc, Parallel sẽ chia công việc thành n phần, chúng xử lý song song nhưng thực tế không phải vậy khi mà các Thread phải đợi nhau để đồng bộ dữ liệu hoặc truyền data cho nhau. Kết quả phụ thuộc vào thứ tự công việc của từng Thread.
Thực tế: Một đám ruộng cần phải gặt song càng sớm càng tốt, chúng ta có 5 người( tức Parallel.For chạy từ 1 - 5) , vấn đề ở đây là chiếc "Liềm" của 5 người đều bị "Cùn" nên cần mài cho "Bén", ngan trái thay chỉ có một cục đá mài nên phải Share nhau. Giả sử trường hợp tệ nhất là 5 cái "Liềm" cùng cần mài trong một thời điểm, thế thì phải xếp hàng đợi mài liềm rồi mới đi gặt(t2); cộng thêm thời gian chia phần đám ruộng(t1) cho 5 người trước khi gặt thì ta có t1 + t2 thời gian. Trong khi nếu trước đó chúng ta chọn phương án là thay nhau gặt lúa thì chúng ta chỉ mất có t2 thời gian. => for.foreach lợi hơn

III> Kết luận
Đây là bài test lý tưởng với Parallel
[code language="csharp"]
namespace ParallelTests
{
class Program
{
private static int Fibonacci(int x)
{
if (x <= 1)
{
return 1;
}
return Fibonacci(x - 1) + Fibonacci(x - 2);
}
private static void DummyWork()
{
var result = Fibonacci(10);
// inspect the result so it is no optimised away.
// We know that the exception is never thrown. The compiler does not.
if (result > 300)
{
throw new Exception("failed to to it");
}
}
private const int TotalWorkItems = 2000000;
private static void SerialWork(int outerWorkItems)
{
int innerLoopLimit = TotalWorkItems / outerWorkItems;
for (int index1 = 0; index1 < outerWorkItems; index1++)
{
InnerLoop(innerLoopLimit);
}
}
private static void InnerLoop(int innerLoopLimit)
{
for (int index2 = 0; index2 < innerLoopLimit; index2++)
{
DummyWork();
}
}
private static void ParallelWork(int outerWorkItems)
{
int innerLoopLimit = TotalWorkItems / outerWorkItems;
var outerRange = Enumerable.Range(0, outerWorkItems);
Parallel.ForEach(outerRange, index1 =>
{
InnerLoop(innerLoopLimit);
});
}
private static void TimeOperation(string desc, Action operation)
{
Stopwatch timer = new Stopwatch();
timer.Start();
operation();
timer.Stop();
string message = string.Format("{0} took {1:mm}:{1:ss}.{1:ff}", desc, timer.Elapsed);
Console.WriteLine(message);
}
static void Main(string[] args)
{
TimeOperation("serial work: 1", () => Program.SerialWork(1));
TimeOperation("serial work: 2", () => Program.SerialWork(2));
TimeOperation("serial work: 3", () => Program.SerialWork(3));
TimeOperation("serial work: 4", () => Program.SerialWork(4));
TimeOperation("serial work: 8", () => Program.SerialWork(8));
TimeOperation("serial work: 16", () => Program.SerialWork(16));
TimeOperation("serial work: 32", () => Program.SerialWork(32));
TimeOperation("serial work: 1k", () => Program.SerialWork(1000));
TimeOperation("serial work: 10k", () => Program.SerialWork(10000));
TimeOperation("serial work: 100k", () => Program.SerialWork(100000));
TimeOperation("parallel work: 1", () => Program.ParallelWork(1));
TimeOperation("parallel work: 2", () => Program.ParallelWork(2));
TimeOperation("parallel work: 3", () => Program.ParallelWork(3));
TimeOperation("parallel work: 4", () => Program.ParallelWork(4));
TimeOperation("parallel work: 8", () => Program.ParallelWork(8));
TimeOperation("parallel work: 16", () => Program.ParallelWork(16));
TimeOperation("parallel work: 32", () => Program.ParallelWork(32));
TimeOperation("parallel work: 64", () => Program.ParallelWork(64));
TimeOperation("parallel work: 1k", () => Program.ParallelWork(1000));
TimeOperation("parallel work: 10k", () => Program.ParallelWork(10000));
TimeOperation("parallel work: 100k", () => Program.ParallelWork(100000));
Console.WriteLine("done");
Console.ReadLine();
}
}
}
[/code]
Kết quả
[code language="c"]
serial work: 1 took 00:02.31
serial work: 2 took 00:02.27
serial work: 3 took 00:02.28
serial work: 4 took 00:02.28
serial work: 8 took 00:02.28
serial work: 16 took 00:02.27
serial work: 32 took 00:02.27
serial work: 1k took 00:02.27
serial work: 10k took 00:02.28
serial work: 100k took 00:02.28
parallel work: 1 took 00:02.33
parallel work: 2 took 00:01.14
parallel work: 3 took 00:00.96
parallel work: 4 took 00:00.78
parallel work: 8 took 00:00.84
parallel work: 16 took 00:00.86
parallel work: 32 took 00:00.82
parallel work: 64 took 00:00.80
parallel work: 1k took 00:00.77
parallel work: 10k took 00:00.78
parallel work: 100k took 00:00.77
[/code]
Parallel là một giải pháp tốt cho multi-Thread và multi-Core, nhưng chúng ta không nên lạm dụng. Hãy hiểu rõ bản chất vấn đề và hiện thực bạn đang đối mặt để chọn gải pháp tốt nhất. Phải chắc chắn rằng bài toán ban đang giải quyết là một bài toán Parallelizable thì mới dùng parallel. Cuối cùng, nên nhớ rằng "đừng lấy dao mổ trâu đi giết gà" và ngược lại. Trên đây là sự hiểu biết cá nhân, rất mong được góp ý.
Phạm Tuân
Thứ Hai, 9 tháng 3, 2015
Hôm nay tôi sẽ giới thiệu với các bạn một Framework đến từ Microsoft - framework này không quá lớn nhưng là khá mạnh khi các bạn làm việc với Module/Plug-in trong một dự án .NET thuần túy. Hy vọng chúng ta sẽ hiểu về "Managed Extensibility Framework" hay còn gọi là MEF phần nào qua bài viết này.
I> MEF là gì
Như tôi nói ở trên, MEF được Microsoft phát triển và bổ sung vào .NET 4.0 và cao hơn vì thế chúng ta không cần phải tải thêm DLLs cũng như không phải có một cấu hình phức tạp nào để có thể sử dụng nó. MEF cho phép chúng ta làm việc theo dạng module một cách đơn giản, giảm thiểu sự ràng buộc giữa các lớp các đối tượng với nhau. Trước kia khi mà MEF chưa ra đời chúng ta thường chật vật với một dự án với quá nhiều thành phần liên kết với nhau, việc khởi tạo cũng như quản lý các thành phần này vô cùng phức tạp cho các nhà phát triển phần mềm, sau đó MS đã cho ra mắt ý tưởng là module hóa các dự án với nhiều thành phần gắn kết nhau - thứ nhất chúng ta sẽ có cái nhìn rõ ràng và sáng sủa hơn, thứ hai việc nâng cấp và mở rộng ứng dụng cũng trở nên dễ dàng hơn khi mà mã nguồn không cần phải thay đổi nhiều. Các bạn có thể hình dung qua hình ảnh sau:
Nếu các bạn để ý, MEF có phần giống với IoC, về mặt nào đó chúng giống nhau song lại không trùng lắp, MEF có một số chức năng thua xa IoC nhưng nó không bị đào thải vì nó có điểm mạnh riêng và không quá phức tạp cho việc cài đặt..png)
II> Ví dụ ứng dụng
Trước tiên chúng ta cần tạo hai project, một là "Program console" và một là "Library" như sau, trong đó ModuleA là "Library project"
Để có thể sử dụng được MEF chúng ta cần add reference cho hai project như hình trên
- "Microsoft.Practices.ServiceLocation"
- "System.ComponentModel.Composition"
Tiến hành cấu hình MEF cho "RUN.MEF project" bằng cách tạo class "MEFServiceLocator" kế thừa "ServiceLocatorImplBase", class này khi được tạo ra sẽ chịu trách nhiệm cung cấp Instance khi cần thiết.
MEFServiceLocator.cs
[code language="csharp"]
internal class MEFServiceLocator : ServiceLocatorImplBase
{
/// <summary>
/// MEF Compisition container
/// </summary>
private readonly CompositionContainer compositionContainer;
/// <summary>
/// Initializes a new instance of the <see cref="MEFServiceLocator"/> class.
/// </summary>
/// <param name="compositionContainer">The MEF composition container.</param>
public MEFServiceLocator(CompositionContainer compositionContainer)
{
this.compositionContainer = compositionContainer;
}
/// <summary>
/// Resolves the instance of the requested service.
/// </summary>
/// <param name="serviceType">Type of instance requested.</param>
/// <returns>
/// The requested service instance.
/// </returns>
protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
{
var list = new List<object>();
IEnumerable<Lazy<object, object>> exports = this.compositionContainer.GetExports(serviceType, null, null);
list.AddRange(exports.Select(export => export.Value));
return list;
}
/// <summary>
/// Resolves all the instances of the requested service.
/// </summary>
/// <param name="serviceType">Type of service requested.</param><param name="key">Name of registered service you want. May be null.</param>
/// <returns>
/// Sequence of service instance objects.
/// </returns>
protected override object DoGetInstance(Type serviceType, string key)
{
List<Lazy<object, object>> exports = this.compositionContainer.GetExports(serviceType, null, key).ToList();
IOrderedEnumerable<Lazy<object, object>> orderByDescending = exports
.Where(export => ((IDictionary<string, object>) export.Metadata).ContainsKey("Priority"))
.OrderByDescending(export => ((IDictionary<string, object>) export.Metadata)["Priority"]);
if (orderByDescending.Any())
{
return orderByDescending.First().Value;
}
if (exports.Any())
{
if (exports.Count > 1)
{
return exports[0].Value;
}
return exports.Single().Value;
}
throw new ActivationException(
this.FormatActivationExceptionMessage(new CompositionException("Export not found"), serviceType, key));
}
[/code]
Quay lại "ModuleA project" chúng ta define một interface và một lớp implement cho interface này như sau
IHello.cs
[code language="csharp"]
public interface IHelloWorld
{
/// <summary>
/// Say hello
/// </summary>
void Hello();
}
[/code]
Hello.cs
[code language="csharp"]
[Export(typeof (IHelloWorld))]
public class HelloWorld : IHelloWorld
{
private int count = 0;
/// <summary>
/// Say hello
/// </summary>
public void Hello()
{
Console.WriteLine("{0} - Hello world", count);
count++;
}
}
[/code]
Chú ý rằng : các module bạn muốn MEF quản lý và làm việc thì cần phải "Export", nếu trường hợp bạn có nhiều implement cho một interface thì bạn cần export thêm key và khi getInstance cần truyền vào key đó(xem thêm ở SourceCode kèm theo)
Và bây giờ chúng ta sẽ sử dụng MEF, quay về class program.cs của "RUN.MEF", trước khi làm việc gì đó chúng ta cần Created Data cho Container, ở đây tôi làm việc đó qua hàm Create(), ở hàm này cứ mỗi module tương ứng một file Dll và tôi sẽ load file này lên và watch tất cả các Class được "Export". Tại hàm Main() tôi dùng như bình thường nhưng các bạn có để ý rằng tôi không hề new một object nào và gán cho property trước khi dùng chúng mà các property được set giá trị bằng "ServiceLocator.Current.GetInstance();" - đó là điều quan trọng và ở bất kỳ đâu trong ứng dụng, bạn chỉ cần get instance như thế là lấy đc instance mong muốn và không cần thông qua các obj khác(instance này có thể được nhiều instance khác dùng chung và updated data bằng cách này).
Program.cs
[code language="csharp"]
public class Program
{
private static CompositionContainer _compositionContainer;
private static IHelloWorld ModuleA
{
get { return ServiceLocator.Current.GetInstance<IHelloWorld>(); }
}
private static void Main(string[] args)
{
Create();
ModuleA.Hello();
ModuleA.Hello();
Console.ReadLine();
}
public static void Create()
{
var aggregateCatalog = new AggregateCatalog();
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory,
"ModuleA.Dll"));
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory,
"ModuleB.Dll"));
_compositionContainer = new CompositionContainer(aggregateCatalog);
var mefServiceLocator = new MEFServiceLocator(_compositionContainer);
ServiceLocator.SetLocatorProvider(() => mefServiceLocator);
}
}
[/code]
Còn tiếp...
Sourcecode tại đây, chúc các bạn thành công!
Phạm Tuân
Chủ Nhật, 8 tháng 3, 2015
Hôm nay chúng ta sẽ thử đo xem thời gian query Data của "Foreach vs LinQ vs LinQasParallel" như thế nào. Tôi tạo một tool nhỏ để làm việc này.
I> Testing
Bài test 1: Tôi tạo một tool với chức năng là tìm trong một List<int> các số chia hết cho 9, tôi dùng thuật toán vét cạn với độ phức tạp là O(n). Các bạn có thể kiểm tra cách tôi làm sau đây
[code language="csharp"]
IList<int> lstData = new List<int>();
private IList<int> GenerateList(Double max)
{
IList<int> result = new List<int>();
var rand = new Random();
for (int i = 0; i < max; i++)
{
var randInt = rand.Next(50000);
result.Add(randInt);
}
return result;
}
// Executing for test
private void CheckingWithLinqAsParallel()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();
countResult = (from itemValue in lstData.AsParallel()
where (CheckingValue(itemValue))
select itemValue).ToList();
// Stop timing
stopwatch.Stop();
// Write result
txtResult.Text = countResult.Count.ToString();
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}
private void CheckingWithLinq()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();
countResult = (from itemValue in lstData
where (CheckingValue(itemValue))
select itemValue).ToList();
// Stop timing
stopwatch.Stop();
// Write result
txtResult.Text = countResult.Count.ToString();
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}
private void CheckingWithForeach()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();
foreach(var itemValue in lstData)
{
if (CheckingValue(itemValue))
{
countResult.Add(itemValue);
}
}
// Stop timing
stopwatch.Stop();
// Write result
txtResult.Text = countResult.Count.ToString();
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}
[/code]
Kết quả test:
[code language="c"]
--------------- O(1000) -----------------
Foreach: Time = 133
Linq: Time = 13246
LinqAsParallel: Time = 69348
--------------- O(900000) -----------------
Foreach: Time = 31887
Linq: Time = 25718
LinqAsParallel: Time = 27008
--------------- O(9000000) -----------------
Foreach: Time = 314022
Linq: Time = 263684
LinqAsParallel: Time = 265144
LinqAsParallel: Time = 302023
LinqAsParallel: Time = 264581
Linq: Time = 259860
Linq: Time = 261858
--------------- O(90000000) -----------------
Foreach: Time = 3361146
Linq: Time = 2707332
LinqAsParallel: Time = 2717361
LinqAsParallel: Time = 3562806
LinqAsParallel: Time = 3608036
Linq: Time = 2751347
LinqAsParallel: Time = 2726033
[/code]
Tạm luận: Chúng ta có thể thấy rằng xét về mặt thời gian
- Khi lượng dữ liệu nhỏ và vừa thì Foreach trội hơn nhiều so với LinQ và LinQAsParallel
- Khi lượng dữ liệu lớn dần chạm một ngưỡng nào đó thì Foreach xuống dần phong độ so với LinQ và LinQAsParallel, dữ liệu càng tăng Foreach càng chậm.
- Giữa LinQ và LinQAsParallel, khi Parallel làm việc, nó phụ thuộc nhiều vào sự nhàn rỗi của CPU vì vậy tốc độ không ổn định, khi dữ liệu quá nhỏ Parallel trở nên chậm đáng kể, khi dữ liệu vừa phải Parallel trở nên rất nhanh - vừa phải ở đây phụ thuộc vào khả năng cung ứng Thread của CPU, khi dữ liệu tăng cao, vượt quá lượng Thread mà CPU hiện tại có thể cung cấp - Parallel trở nên chậm lại(Chú ý rằng công việc này là cần sự đồng bộ data).
Bài test 2: Độ phức tạp được nâng lên O(500^n) tức có hai vòng for lòng nhau, 500 là số lần lập cho vòng for trong, tôi không thể test với dữ liệu lớn đáng kể vì sẽ bị ERROR "out of memmory", lượng RAM mà tool chiếm > 2.2GB
[code language="csharp"]
IDictionary<int, IList<int>> lstDataUP = new Dictionary<int, IList<int>>();
private Dictionary<int, IList<int>> GenerateListUP(Double max)
{
var result = new Dictionary<int, IList<int>>();
var rand = new Random();
for (int i = 0; i < max; i++)
{
IList<int> subresult = new List<int>();
for (int j = 0; j < maxsubItem; j++)
{
var randInt = rand.Next(500);
subresult.Add(randInt);
}
result.Add(i,subresult);
}
return result;
}
// Excuteing for test
private void CheckingWithLinqAsParallelUP()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();
countResult = (from itemValue in lstDataUP.AsParallel()
from subitemValue in itemValue.Value.AsParallel()
where (CheckingValue(subitemValue))
select subitemValue).ToList();
// Stop timing
stopwatch.Stop();
txtResult.Text = countResult.Count.ToString();
// Write result
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}
private void CheckingWithLinqUP()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();
countResult = (from itemValue in lstDataUP
from subitemValue in itemValue.Value
where (CheckingValue(subitemValue))
select subitemValue).ToList();
// Stop timing
stopwatch.Stop();
txtResult.Text = countResult.Count.ToString();
// Write result
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}
private void CheckingWithForeachUP()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();
foreach (var itemValue in lstDataUP)
{
foreach (var subitemValue in itemValue.Value)
{
if (CheckingValue(subitemValue))
{
countResult.Add(subitemValue);
}
}
}
// Stop timing
stopwatch.Stop();
txtResult.Text = countResult.Count.ToString();
// Write result
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}
[/code]
Kết quả test
[code language="c"]
--------------- O(500^1000) -----------------
Foreach: Time = 99412
Linq: Time = 1085896
LinqAsParallel: Time = 3105398
--------------- O(500^9000) -----------------
Foreach: Time = 202546
Linq: Time = 695352
LinqAsParallel: Time = 582843
--------------- O(500^90000) -----------------
Foreach: Time = 1605131
Linq: Time = 5647985
LinqAsParallel: Time = 4189570
--------------- O(500^100000) -----------------
Foreach: Time = 1784048
Linq: Time = 6613424
LinqAsParallel: Time = 4496013
--------------- O(500^500000) -----------------
Foreach: Time = 9376404
Linq: Time = 32156749
LinqAsParallel: Time = 22864588
[/code]
Tạm luận: qua bài test trên chúng ta thấy rằng
- Foreach luôn chiếm ưu thế trong tình huống này, dữ liệu càng lớn sự chênh lệch càng rõ
- Giữa LinQ và LinQAsParallel, khi dữ liệu lớn đến một mốc nào đó LinQAsParallel tỏ ra nhanh hơn và ngược lại
- Khi chúng ta quan tâm đến một vài điều kiện để cho phép vòng for con thực thi hoặc không thì sự chênh lệch giữa LinQ và LinQAsParallel càng xa
II> Kết luận
Như vậy, chúng ta có thể tạm kết luận rằng LinQ nhanh hơn hẳn Foreach nếu dữ liệu lớn đáng kể(> trăm ngàn dòng) và độ phức tạp tương đương O(n), nó thật sự chậm chạp khi dữ liệu là nhỏ ở mức vài ngàn dòng; trong khi đó nếu độ phức tạp tăng lên LinQ không phải là một sự lựu chọn tối ưu về tốc độ nếu đứng cạnh Foreach. Hãy cân nhắc đến việc sử dụng LinQ thay cho LinQAsParallel vì Parallel là một con dao hai lưỡi nếu chúng ta không hiểu hết thực trạng hệ thống và khả năng hiện tại của CPU cũng như lượng dữ liệu không cố định. Trong một số trường hợp chúng ta dùng Parallel sẽ bị Exception vì dữ liệu quá lớn trong khi Data cần thiết phải đồng bộ.
Các bài test được thực hiện trên Lap HP probook, I3 và 4G RAM, tình trạng sử dụng tài nguyên không nhiều.
Trên đây là những đánh giá chủ quan về mặt Query data(có đồng bộ) trong một dữ liệu dạng danh sách, tốc độ phụ thuộc nhiều vào thiết kế phần cứng của máy vì vậy hãy tham khảo các kết luận trên như một ý kiến cá nhân, các bạn có thể Comment để thảo luận về đề tài này.
Download SourceCode tại đây, chúc các bạn thành công!
Phạm Tuân
Thứ Ba, 3 tháng 3, 2015
Hôm nay tôi sẽ hướng dẫn các bạn một số kỹ thuật với log4net, 3 kỹ thuật chúng ta cần nắm sau bài này là "Làm sao để tạo các file log riêng cho từng namspace", "Làm sao để các file log tự động được ghi vào thư mục có tên tương ứng với ngày hiện tại" và "làm sao để lọc thông tin cho một file log - nâng cao". Mời các bạn cùng theo dõi.
I> Tạo file riêng cho từng namspace
Để tạo một tập tin/kiểu log chúng ta thêm vào file config một thẻ appender với các thông số cở bản như bài trước và để tiến hành log cho một namspace ta dùng thẻ logger.
Format config như sau
[code language="xml"]
<!-- name của appender là tên bất kỳ-->
<appender name="rootTrace">
<!-- param -->
</appender>
<appender name="MyCompany">
<!-- param -->
</appender>
<appender name="MyCompany.MyCompany1">
<!-- param -->
</appender>
<appender name="MyCompany.MyCompany2">
<!-- param -->
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="rootTrace"/>
</root>
<!-- name của logger chính là namespace muốn lọc-->
<logger name="MyCompany">
<level value="WARN" />
<!-- ref đến một appender-->
<appender-ref ref="MyCompany" />
</logger>
<logger name="MyCompany.MyCompany1">
<level value="WARN" />
<appender-ref ref="MyCompany.MyCompany1" />
</logger>
<logger name="MyCompany.MyCompany2">
<level value="DEBUG" />
<appender-ref ref="MyCompany.MyCompany2" />
</logger>
[/code]
Ví dụ: Chúng ta sẽ có một file log với tên "myLog.log", chứa toàn bộ log sinh ra từ namespace "Namespace.subNamespace" và loglevel = "DEBUG"
[code language="xml"]
<!-- config appender với type = RollingFileAppender-->
<appender name="myApenName" type="log4net.Appender.RollingFileAppender">
<file value="..\log\myLog.log"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maximumFileSize value="25MB"/>
<maxSizeRollBackups value="100"/>
<staticLogFileName value="true"/>
<countDirection value="0"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy/MM/dd HH:mm:ss.fff} %-5level [%-4thread] %logger - %message%newline" />
</layout>
</appender>
<logger name="Namespace.subNamespace">
<level value="DEBUG" />
<!-- refer đến appender bạn muốn-->
<appender-ref ref="myApenName" />
</logger>
<root>
<level value="INFO" />
<!-- <appender-ref ref="myApenName"/> -->
</root>
[/code]
Lưu ý rằng: chúng ta không nên refer đến appender "myApenName" trong thẻ root vì thế này đồng nghĩa với việc log tất cả các namespace
II> Cách tạo các file log trong một thư mục được đặt tên theo ngày
Cụ thể là nếu hôm nay ngày 03/03/2015 thì chúng ta sẽ có thư mục mang tên "03-03-2015", thư mục này chứa tất cả các file log được tạo ra đúng ngày hôm nay.
Xem ví dụ sau đây,
[code language="xml"]
<appender name="Alllog" type="log4net.Appender.RollingFileAppender">
<!-- (1) -->
<param name="File" value="../Log/" />
<param name="DatePattern" value='yyyy-MM-dd"/Alllog.log"' />
<!-- (2) -->
<param name="StaticLogFileName" value="false" />
<param name="RollingStyle" value="Composite" />
<param name="AppendToFile" value="true" />
<param name="MaximumFileSize" value="5MB" />
<param name="MaxSizeRollBackups" value="50" />
<encoding value="utf-8" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy/MM/dd HH:mm:ss.fff} %-5level [%-4thread] %logger - %message%newline" />
</layout>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
[/code]
Các bạn chú ý hai thẻ mà tôi đặc giữa hai số (1) và (2); Dòng đầu chúng ta dùng dấu '/' thay vì '\' như thông thường; Dòng hai là đường dẫn lưu file log - đường dẫn này có một thư mục mà tên được lấy từ ngày hiện tại với format là "yyyy-MM-dd".
III> Các kỹ thuật filter nâng cao trong log4net
Như chúng ta cũng biết ở bài trước, LogLevel cũng là một filter để chúng ta có thể tùy chọn nội dung log phù hợp, kèm theo đó trong mỗi Appender có các filter riêng. Hôm nay, chúng ta sẽ cùng tìm hiểu thêm về filter trong mỗi Appender .
Nhắc lại: bài trước nói về thẻ filter tôi nhắc đến ba loại như sau: StringMatchFilter, LevelRangeFilter, LevelMatchFilter, DenyAllFilter; Hôm nay chúng ta có thêm một loại nữa là "LoggerMatchFilter"
1) Cách tạo Whitelisting by logger - danh sách các namespace được phép ghi log
Format như sau
[code language="xml"]
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="myNamspace.comm" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
[/code]
Bộ lọc LoggerMatchFilter mặc định giá trị của acceptOnMatch là True, tức là bộ lọc này là bộ lọc cho phép(các giá trị thõa điều kiện của bộ lọc này thì sẽ được ghi ra file). Ví dụ rên đây sẽ chỉ ghi ra log cho các nội dung log thuộc namespace là "myNamspace.comm", vì tất cả các log khác bị cấm (DenyAllFilter)
2) Cách tạo Blacklisting by logger - danh sách các namespace bị cấm ghi log
Format như sau
[code language="xml"]
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="LoggerDeny" />
<acceptOnMatch value="false" />
</filter>
[/code]
Tất cả các log đều được ghi trừ log phát sinh tại namespace "LoggerDeny", chúng ta làm điều này bằng cách set giá trị cho thẻ acceptOnMatch là False
3) Cách tạo bộ LoggerMatchFilter gộp
* Format của việc gộp các LoggerMatchFilter
[code language="xml"]
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="Noisy.Namespace.But.Important" />
</filter>
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="Noisy.Namespace" />
<acceptOnMatch value="false" />
</filter>
[/code]
Ví dụ trên cho ta một file log chứa các log của namespace "Noisy.Namespace.But.Important" và các log của namespace "Noisy.Namespace" bị cấm ghi -> Song thực tế chúng ta không mấy khi dùng kiểu gộp này.
* Format của việc gộp LoggerMatchFilter với LevelRangeFilter hoặc StringMatchFilter
[code language="xml"]
<appender name="AllLog" type="log4net.Appender.RollingFileAppender">
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="myNamespace" />
<acceptOnMatch value="false" />
</filter>
<filter type="log4net.Filter.LevelRangeFilter">
<acceptOnMatch value="true" />
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>
<!-- param...-->
</appender>
<appender name="AllLog" type="log4net.Appender.RollingFileAppender">
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="myNamespace" />
</filter>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
<!-- param...-->
</appender>
[/code]
Ví dụ này ghi tất cả các log có level từ INFO->FATAL và trừ log của namespace "myNamespace" hoặc ghi tất cả các log của namespace "myNamespace" và các log có chứa "test".
Chú ý rằng: nếu trong bộ gộp các thẻ filter có kiểu LoggerMatchFilter chúng ta nên để thẻ filter này lên trên các thẻ filter khác.
Chúc các bạn thành công,
Phạm Tuân
Chủ Nhật, 8 tháng 2, 2015
Design Pattern là một kỹ thuật trong lập trình hướng đối tượng, nó khá quan trọng và mọi lập trình viên muốn giỏi đều phải biết. Được sử dụng thường xuyên trong các ngôn ngữ OOP. Nó sẽ cung cấp cho bạn các "mẫu thiết kế", giải pháp để giải quyết các vấn đề chung, thường gặp trong lập trình. Các vấn đề mà bạn gặp phải có thể bạn sẽ tự nghĩ ra cách giải quyết nhưng có thể nó chưa phải là tối ưu. Design Pattern giúp bạn giải quyết vấn đề một cách tối ưu nhất, cung cấp cho bạn các giải pháp trong lập trình OOP.
Design Pattern có 23 mẫu cơ bản(GoF) và hình dưới là mối quang hệ giữa chúng
Trong Design Pattern có 3 nhóm bao gồm:
Creational Pattern (nhóm khởi tạo) gồm: Abstract Factory, Factory Method, Singleton, Builder, Prototype. Nó sẽ giúp bạn trong việt khởi tạo đối tượng, như bạn biết để khởi tạo bạn phải sử dụng từ khóa new, nhóm Creational Pattern sẽ sử dụng một số thủ thuật để khởi tạo đối tượng mà bạn sẽ không nhìn thấy từ khóa này.
Structural Pattern (nhóm cấu trúc) gồm: Adapter, Bridge, Composite, Decorator, Facade, Proxy và Flyweight.. Nó dùng để thiết lập, định nghĩa quan hệ giữa các đối tượng.
Behavioral Pattern gồm: Interpreter, Template Method, Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy và Visitor. Nhóm này dùng trong thực hiện các hành vi của đối tượng.
Design Pattern giúp bạn tái sử dụng mã lệnh và dễ dàng mở rộng. Để chuẩn bị học Design Pattern bạn cần phải nắm chắc các khái niệm sau:
- Bốn đặc tính của OOP: Thừa kế, Đa hình, Trừu tượng, Bao đóng.
- Khái niệm interface và abstract. Cái này cực kỳ quan trọng, để hiểu và áp dụng 2 khái niệm này có thể sẽ mất một thời gian, nhưng khi bạn nắm chắc nó bạn sẽ thấy nó thực sự cần thiết.
- Bỏ tư duy theo lối cấu trúc, nâng tư duy hoàn toàn OOP.
Tại sao sử dụng Design Pattern?
Design pattern cung cấp giải pháp ở dạng tổng quát, giúp tăng tốc độ phát triển phần mềm bằng cách đưa ra các mô hình test, mô hình phát triển đã qua kiểm nghiệm. Thiết kế phần mềm hiệu quả đòi hỏi phải cân nhắc các vấn đề sẽ nảy sinh trong quá trình hiện thực hóa (implementation). Dùng lại các design pattern giúp tránh được các vấn đề tiềm ẩn có thể gây ra những lỗi lớn, dễ dàng nâng cấp, bảo trì về sau.
Một lợi thế lớn để sử dụng một mẫu thiết kế là lập trình viên khác sẽ có thể dễ dàng nhận ra nó (đặc biệt là nếu bạn sử dụng quy ước đặt tên tốt).
Khi nào nên sử dụng Design pattern?
Đó là khi bạn muốn giữ cho chương trình của mình thực sự đơn giản. Việc sử dụng các design pattern sẽ giúp chúng ta giảm được thời gian và công sức suy nghĩ ra các cách giải quyết cho những vấn đề đã có lời giải. Bạn có thể đọc qua cuốn “Head First Design Patterns” để có cái nhìn tổng quát hơn về design pattern.
Hệ thống các mẫu design pattern chuẩn hiện có 23 mẫu được định nghĩa trong cuốn "Design patterns Elements of Reusable Object Oriented Software" mà khởi đầu là cuốn "Gang of Four (GoF)". Các tác giả của cuốn sách là Erich Gamma, Richard Helm, Ralph Johnson và John Vlissides, hay còn được biết đến với các tên "Gang of Four" hay đơn giản là "GoF". Hệ thống các mẫu này có thể nói là đủ và tối ưu cho việc giải quyết hết các vấn đề của bài toán phân tích thiết kế và xây dựng phần mềm trong thời điểm hiện tại. Hệ thống các mẫu design pattern được chia thành 3 nhóm: nhóm Creational (5 mẫu), nhóm Structural (7 mẫu) và nhóm Behavioral (11 mẫu).
| STT | Tên | Mục đích |
| Nhóm Creational (nhóm kiến tạo) | ||
| 1 | Abstract Factory | Cung cấp một interface cho việc tạo lập các đối tượng (có liên hệ với nhau) mà không cần qui định lớp khi hay xác định lớp cụ thể (concrete) tạo mỗi đối tượngTần suất sử dụng: cao |
| 2 | Builder | Tách rời việc xây dựng (construction) một đối tượng phức tạp khỏi biểu diễn của nó sao cho cùng một tiến trình xây dựng có thể tạo được các biểu diễn khác nhau.Tần suất sử dụng: trung bình thấp |
| 3 | Factory Method | Định nghĩa Interface để sinh ra đối tượng nhưng để cho lớp con quyết định lớp nào được dùng để sinh ra đối tượng Factory method cho phép một lớp chuyển quá trình khởi tạo đối tượng cho lớp con.Tần suất sử dụng: cao |
| 4 | Prototype | Qui định loại của các đối tượng cần tạo bằng cách dùng một đối tượng mẫu, tạo mới nhờ vào sao chép đối tượng mẫu này.Tần suất sử dụng: trung bình |
| 5 | Singleton | Đảm bảo 1 class chỉ có 1 instance và cung cấp 1 điểm truy xuất toàn cục đến nó.Tần suất sử dụng: cao trung bình |
| Nhóm Structural (nhóm cấu trúc) | ||
| 6 | Adapter | Do vấn đề tương thích, thay đổi interface của một lớp thành một interface khác phù hợp với yêu cầu người sử dụng lớp.Tần suất sử dụng: cao trung bình |
| 7 | Bridge | Tách rời ngữ nghĩa của một vấn đề khỏi việc cài đặt ; mục đích để cả hai bộ phận (ngữ nghĩa và cài đặt) có thể thay đổi độc lập nhau.Tần suất sử dụng: trung bình |
| 8 | Composite | Tổ chức các đối tượng theo cấu trúc phân cấp dạng cây; Tất cả các đối tượng trong cấu trúc được thao tác theo một cách thuần nhất như nhau.Tạo quan hệ thứ bậc bao gộp giữa các đối tượng. Client có thể xem đối tượng bao gộp và bị bao gộp như nhau -> khả năng tổng quát hoá trong code của client -> dễ phát triển, nâng cấp, bảo trì.Tần suất sử dụng: cao trung bình |
| 9 | Decorator | Gán thêm trách nhiệm cho đối tượng (mở rộng chức năng) vào lúc chạy (dynamically).Tần suất sử dụng: trung bình |
| 10 | Facade | Cung cấp một interface thuần nhất cho một tập hợp các interface trong một “hệ thống con” (subsystem). Nó định nghĩa 1 interface cao hơn các interface có sẵn để làm cho hệ thống con dễ sử dụng hơn.Tần suất sử dụng: cao |
| 11 | Flyweight | Sử dụng việc chia sẻ để thao tác hiệu quả trên một số lượng lớn đối tượng “cở nhỏ” (chẳng hạn paragraph, dòng, cột, ký tự…).Tần suất sử dụng: thấp |
| 12 | Proxy | Cung cấp đối tượng đại diện cho một đối tượng khác để hỗ trợ hoặc kiểm soát quá trình truy xuất đối tượng đó. Đối tượng thay thế gọi là proxy.Tần suất sử dụng: cao trung bình |
| Nhóm Behavioral (nhóm tương tác) | ||
| 13 | Chain of Responsibility | Khắc phục việc ghép cặp giữa bộ gởi và bộ nhận thông điệp; Các đối tượng nhận thông điệp được kết nối thành một chuỗi và thông điệp được chuyển dọc theo chuỗi nầy đến khi gặp được đối tượng xử lý nó.Tránh việc gắn kết cứng giữa phần tử gởi request với phần tử nhận và xử lý request bằng cách cho phép hơn 1 đối tượng có có cơ hội xử lý request . Liên kết các đối tượng nhận request thành 1 dây chuyền rồi “pass” request xuyên qua từng đối tượng xử lý đến khi gặp đối tượng xử lý cụ thể.Tần suất sử dụng: trung bình thấp |
| 14 | Command | Mỗi yêu cầu (thực hiện một thao tác nào đó) được bao bọc thành một đối tượng. Các yêu cầu sẽ được lưu trữ và gởi đi như các đối tượng.Đóng gói request vào trong một Object , nhờ đó có thể nthông số hoá chương trình nhận request và thực hiện các thao tác trên request: sắp xếp, log, undo…Tần suất sử dụng: cao trung bình |
| 15 | Interpreter | Hỗ trợ việc định nghĩa biểu diễn văn phạm và bộ thông dịch cho một ngôn ngữ.Tần suất sử dụng: thấp |
| 16 | Iterator | Truy xuất các phần tử của đối tượng dạng tập hợp tuần tự (list, array, …) mà không phụ thuộc vào biểu diễn bên trong của các phần tử.Tần suất sử dụng: cao |
| 17 | Mediator | Định nghĩa một đối tượng để bao bọc việc giao tiếp giữa một số đối tượng với nhau.Tần suất sử dụng: trung bình thấp |
| 18 | Memento | Hiệu chỉnh và trả lại như cũ trạng thái bên trong của đối tượng mà vẫn không vi phạm việc bao bọc dữ liệu.Tần suất sử dụng: thấp |
| 19 | Observer | Định nghĩa sự phụ thuộc một-nhiều giữa các đối tượng sao cho khi một đối tượng thay đổi trạng thái thì tất cả các đối tượng phụ thuộc nó cũng thay đổi theo.Tần suất sử dụng: cao |
| 20 | State | Cho phép một đối tượng thay đổi hành vi khi trạng thái bên trong của nó thay đổi , ta có cảm giác như class của đối tượng bị thay đổi.Tần suất sử dụng: trung bình |
| 21 | Strategy | Bao bọc một họ các thuật toán bằng các lớp đối tượng để thuật toán có thể thay đổi độc lập đối với chương trình sử dụng thuật toán.Cung cấp một họ giải thuật cho phép client chọn lựa linh động một giải thuật cụ thể khi sử dụng.Tần suất sử dụng: cao trung bình |
| 22 | Template method | Định nghĩa phần khung của một thuật toán, tức là một thuật toán tổng quát gọi đến một số phương thức chưa được cài đặt trong lớp cơ sở; việc cài đặt các phương thức được ủy nhiệm cho các lớp kế thừa.Tần suất sử dụng: trung bình |
| 23 | Visitor | Cho phép định nghĩa thêm phép toán mới tác động lên các phần tử của một cấu trúc đối tượng mà không cần thay đổi các lớp định nghĩa cấu trúc đó.Tần suất sử dụng: thấp |
Bạn có thể học Design pattern ở đâu
- Cuốn sách nổi tiếng nhất và là khởi đầu của Design pattern là "Gang of Four (GoF)"
- Trang mạng uy tín để bạn có thể học thêm ngoài 23 mẫu cơ bản là "Sourcemaking.com" có code mẫu
- Kênh youtube có "Derek Banas", "Mohammad Alqerm" và khá nhiều kênh khác
Đây là source một số pattern thông dụng theo kinh nghiệm của tôi.
Chúc các bạn thành công!
Phạm Tuân
Trích theo mr.datnh
Thứ Năm, 22 tháng 1, 2015
Trong một số trường hợp, chúng ta muốn liên tục cập nhật thông tin về sự thay đổi của một tập tin hoặc thư mục nào đó cho người dùng và chúng ta không muốn dùng While(true) để liên tục quét đối tượng xem có thay đổi gì hay không thì cách làm sau đây là tối ưu cho nhu cầu trên.
Chúng ta sẽ tìm hiểu về FileSystemWatcher- lớp này cho phép chúng ta lắng nghe mọi sự thay đổi của một tập tin hoặc một thư mục trên HDD, mọi sự thay đối có thể là: Xóa, Đổi tên, Thêm mới, Sửa nội dung, ...
Mời các bạn xem qua ví dụ sau
[code language="csharp"]
static void Main(string[] args)
{
//Watching thư mục
RunWatcher();
Console.Read();
}
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public static void RunWatcher()
{
//Get đường dẫn
string path = System.Environment.CurrentDirectory;
//Check đường dẫn
if (path.Length < 2)
{
Console.WriteLine("Directory is not correct");
return;
}
//Tạo FileSystemWatcher
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = path;
//Chỉ watch cho các file txt
//watcher.Filter = "*.txt";
//Chỉ watch cho all file
watcher.Filter = "*.*";
//Đăng ký event
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
//Begin watching.
watcher.EnableRaisingEvents = true;
Console.WriteLine("Directory is {0}",path);
}
//Define event
private static void OnChanged(object source, FileSystemEventArgs e)
{
Console.WriteLine("File: " + e.FullPath + " " + e.ChangeType);
}
private static void OnRenamed(object source, RenamedEventArgs e)
{
Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, e.FullPath);
}
[/code]
Chúc các bạn thành công!
Phạm Tuân
Thứ Sáu, 16 tháng 1, 2015
Để dễ hiểu, các bạn hãy hình dung rằng: cùng một tập tin thực thi *.EXE nhưng bạn DoubleClick 10 lần cách quãng vào nó thì chuyện gì sẽ sảy ra => bạn sẽ có 10 chương trình giống nhau trên DesckTop, trong một số trường hợp điều này là không được phép, vậy làm thế nào để thông báo cho người dùng rằng ứng dụng họ muốn thực thi đã được khởi động và đang làm việc trên hệ thống. Cùng tìm cách...
I> Cách I
Có rất nhiều cách trong đó có việc lấy danh sách các ứng dụng đang chạy trên hệ thống và kiểm tra ID ứng dụng người dùng muốn khởi động đã có hay chưa. Xem ví dụ sau.
Tạo một lớp mới với nội dung sau đây:
[code language="csharp"]
static class ProcessChecker
{
static string _requiredString;
internal static class NativeMethods
{
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProcDel lpEnumFunc,
Int32 lParam);
[DllImport("user32.dll")]
public static extern int GetWindowThreadProcessId(IntPtr hWnd,
ref Int32 lpdwProcessId);
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString,
Int32 nMaxCount);
public const int SW_SHOWNORMAL = 1;
}
public delegate bool EnumWindowsProcDel(IntPtr hWnd, Int32 lParam);
static private bool EnumWindowsProc(IntPtr hWnd, Int32 lParam)
{
int processId = 0;
NativeMethods.GetWindowThreadProcessId(hWnd, ref processId);
StringBuilder caption = new StringBuilder(1024);
NativeMethods.GetWindowText(hWnd, caption, 1024);
// Use IndexOf to make sure our required string is in the title.
if (processId == lParam && (caption.ToString().IndexOf(_requiredString,
StringComparison.OrdinalIgnoreCase) != -1))
{
// Restore the window.
NativeMethods.ShowWindowAsync(hWnd, NativeMethods.SW_SHOWNORMAL);
NativeMethods.SetForegroundWindow(hWnd);
}
return true; // Keep this.
}
static public bool IsOnlyProcess(string forceTitle)
{
_requiredString = forceTitle;
foreach (Process proc in Process.GetProcessesByName(Application.ProductName))
{
if (proc.Id != Process.GetCurrentProcess().Id)
{
NativeMethods.EnumWindows(new EnumWindowsProcDel(EnumWindowsProc),
proc.Id);
return false;
}
}
return true;
}
}
[/code]
Sửa dụng như sau,
[code language="csharp"]
static void Main()
{
if (ProcessChecker.IsOnlyProcess("Windows Text")) //Nội dung này không quá quan trọng
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
#region new instance
/////////////////////////
Application.Run(new Form1());
////////////////////////
#endregion
}
else
{
MessageBox.Show("this App is runing");
Application.Exit();
}
}
[/code]
II> Cách II
Cùng ý tưởng cách 1 nhưng ngắn gọn hơn nhiều
[code language="csharp"]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Process currentProcess = Process.GetCurrentProcess();
Process[] processItems = Process.GetProcessesByName(currentProcess.ProcessName);
foreach (Process item in processItems)
{
if (item.Id != currentProcess.Id)
{
MessageBox.Show("Another instance running");
Application.Exit();
}
}
#region new instance
/////////////////////////
Application.Run(new Form1());
////////////////////////
#endregion
}
[/code]
III> Cách III
Cách này cao cấp hơn vì thế ngắn gọn hơn nhiều
[code language="csharp"]
static void Main()
{
bool firstInstance;
using (Mutex mutex = new Mutex(true, "Chuỗi Định Danh - Duy nhất", out firstInstance))
{
if (firstInstance)
{
#region new instance
/////////////////////////
Application.Run(new Form1());
////////////////////////
#endregion
}
else
{
MessageBox.Show("Another instance running", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Application.Exit();
}
}
}
[/code]
SourceCode tham khảo tại đây
Chúc các bạn thành công!
Phạm Tuân
Trong Windows, các ứng sử của người dùng và các ứng dụng liên lạc với hệ điều hành(HĐH) thông qua các thông điệp, và "kỹ thuật" Hook chính là chặn bắc các thông điệp ấy phục vụ mục đích cho từng ứng dụng. TRong bài viếc này tôi sẽ làm một ví dụ cụ thể về việc lắng nghe sự kiện nhấn phím(keyboard).
Để hiểu về khái niệm, các bạn có thể tham khảo tài liệu Microsoft tại đây hoặc của một blogger khác tại đây
Dưới đây là hình ảnh một ứng dụng ghi chú có sử dụng phím tắt(hook), ở bấc cứ đâu nếu bạn nhấn ALT->C là dữ liệu trong vùng nhớ đệm(Clipboard) sẽ được Add vào ứng dụng này, để coppy dữ liệu bạn chỉ cần chọn dòng và có thể Ctrl+C để dán, dùng Del/ESC để xóa, khi tắc đi dữ liệu sẽ được lưu ra file và đọc lên cho lần sau. Do nhu cầu công việc nên tôi chọn cách ghi chú theo từng dòng. Dùng thử tại đây
[caption id="attachment_319" align="aligncenter" width="566"]
HoverNote[/caption]Tôi có một ví dụ sau, tôi làm một ứng dụng WPF về cột đèn giao thông(3 màu), và tôi muốn khi nhấn phím R thì đèn đỏ sáng và tương tự với các phím G, Y. Để làm được điều này quả là vô cùng đơn giản với các bạn đã từng làm một App nào đó trên .NET. Xong khi mà App của bạn không được Focus(hoặc không Active) thì xem như không thể sử dụng phím tắt. Chúng ta khắc phục bằng cách dùng Hook, tại ứng dụng của mình, ta đăng ký HOOK_KEYBOARD với HĐH để lắng nghe các thông điệp về Phím, như vậy khi có sự kiện nào về Phím chúng ta sẽ nhận được thông báo từ HĐH dù App không được Focus.
Tôi xây dựng 3 class như sau, để hiểu nó các bạn tham khảo link đầu bài và nhớ reference các thư viện cần thiết.
InterceptKeys: Đăng ký Hook hoặc hủy đăng ký[code language="csharp"]
#region WINAPI Helper class
/// <summary>
/// Winapi Key interception helper class.
/// </summary>
internal static class InterceptKeys
{
public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
public static int WH_KEYBOARD_LL = 13;
public enum KeyEvent : int
{
WM_KEYDOWN = 256,
WM_KEYUP = 257,
WM_SYSKEYUP = 261,
WM_SYSKEYDOWN = 260
}
public static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
}
#endregion
[/code]
RawKeyEventArgs: Truyền tải thông tin cho các sự kiện Keynoard[code language="csharp"]
/// <summary>
/// Raw KeyEvent arguments.
/// </summary>
public class RawKeyEventArgs : EventArgs
{
/// <summary>
/// VKCode of the key.
/// </summary>
public int VKCode;
/// <summary>
/// WPF Key of the key.
/// </summary>
public Key Key;
/// <summary>
/// Is the hitted key system key.
/// </summary>
public bool IsSysKey;
/// <summary>
/// Create raw keyevent arguments.
/// </summary>
/// <param name="VKCode"></param>
/// <param name="isSysKey"></param>
public RawKeyEventArgs(int VKCode, bool isSysKey)
{
this.VKCode = VKCode;
this.IsSysKey = isSysKey;
this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode);
}
}
[/code]
KeyboardListener: Quản lý Hook và các event[code language="csharp"]
public class KeyboardListener : IDisposable
{
/// <summary>
/// Fired when any of the keys is pressed down.
/// </summary>
public event RawKeyEventHandler KeyDown;
/// <summary>
/// Fired when any of the keys is released.
/// </summary>
public event RawKeyEventHandler KeyUp;
/// <summary>
/// Creates global keyboard listener.
/// </summary>
public KeyboardListener()
{
// We have to store the HookCallback, so that it is not garbage collected runtime
hookedLowLevelKeyboardProc = (InterceptKeys.LowLevelKeyboardProc)LowLevelKeyboardProc;
// Set the hook
hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc);
// Assign the asynchronous callback event
hookedKeyboardCallbackAsync = new KeyboardCallbackAsync(KeyboardListener_KeyboardCallbackAsync);
}
/// <summary>
/// Destroys global keyboard listener.
/// </summary>
~KeyboardListener()
{
Dispose();
}
#region Inner workings
/// <summary>
/// Hook ID
/// </summary>
private IntPtr hookId = IntPtr.Zero;
/// <summary>
/// Asynchronous callback hook.
/// </summary>
/// <param name="nCode"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
private delegate void KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode);
/// <summary>
/// Actual callback hook.
///
/// <remarks>Calls asynchronously the asyncCallback.</remarks>
/// </summary>
/// <param name="nCode"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
if (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYUP ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYUP)
hookedKeyboardCallbackAsync.BeginInvoke((InterceptKeys.KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), null, null);
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
}
/// <summary>
/// Event to be invoked asynchronously (BeginInvoke) each time key is pressed.
/// </summary>
private KeyboardCallbackAsync hookedKeyboardCallbackAsync;
/// <summary>
/// Contains the hooked callback in runtime.
/// </summary>
private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc;
/// <summary>
/// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events.
/// </summary>
/// <param name="keyEvent">Keyboard event</param>
/// <param name="vkCode">VKCode</param>
void KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode)
{
switch (keyEvent)
{
// KeyDown events
case InterceptKeys.KeyEvent.WM_KEYDOWN:
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, false));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYDOWN:
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, true));
break;
// KeyUp events
case InterceptKeys.KeyEvent.WM_KEYUP:
if (KeyUp != null)
KeyUp(this, new RawKeyEventArgs(vkCode, false));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYUP:
if (KeyUp != null)
KeyUp(this, new RawKeyEventArgs(vkCode, true));
break;
default:
break;
}
}
#endregion
#region IDisposable Members
/// <summary>
/// Disposes the hook.
/// <remarks>This call is required as it calls the UnhookWindowsHookEx.</remarks>
/// </summary>
public void Dispose()
{
InterceptKeys.UnhookWindowsHookEx(hookId);
}
#endregion
}
[/code]
3 class trên là tất cả những gì chúng ta cần cho ứng dụng của mình. Xong chưa đủ, dưới đây là KeyCode cho các phím, tất cả đều dùng được nhé.
[code language="csharp"]
public class VKCodeConst
{
public const int LEFTCTRL = 162;
public const int RIGHTCTRL = 163;
public const int LEFTALT = 164;
public const int RIGHTALT = 165;
//public const int LButton = 1; //Left mouse button
//public const int RButton = 2; //Right mouse button
//public const int Cancel = 3; //CANCEL key
//public const int MButton = 4; //Middle mouse button
//public const int Back = 8; //BACKSPACE key
//public const int Tab = 9; //TAB key
//public const int Clear = 12; //CLEAR key
//public const int Return = 13; //ENTER key
public const int SHIFT = 160; //SHIFT key
//public const int Control = 17; //CTRL key
//public const int Menu = 18; //MENU key
//public const int Pause = 19; //PAUSE key
//public const int Capital = 20; //CAPS LOCK key
//public const int Escape = 27; //ESC key
//public const int Space = 32; //SPACEBAR key
//public const int PageUp = 33; //PAGE UP key
//public const int PageDown = 34; //PAGE DOWN key
//public const int End = 35; //END key
//public const int Home = 36; //HOME key
//public const int Left = 37; //LEFT ARROW key
//public const int Up = 38; // ARROW key Up
//public const int Right = 39; //RIGHT ARROW key
//public const int Down = 40; //DOWN ARROW key
//public const int Select = 41; //SELECT key
//public const int Print =42; //PRINT SCREEN key
//public const int Execute = 43; //EXECUTE key
//public const int Snapshot = 44; //SNAPSHOT key
//public const int Insert = 45; //INS key
//public const int Delete = 46; //DEL key
//public const int Help = 47; //HELP key
//public const int Numlock = 144; //NUM LOCK key
public const int NUM0 = 48;
public const int NUMPAD0 = 96;
//public const int F1 = 112;
//public const int F2 = 113;
//public const int F3 = 114;
//public const int F4 = 115;
//public const int F5 = 116;
//public const int F6 = 117;
//public const int F7 = 118;
//public const int F8 = 119;
//public const int F9 = 120;
//public const int F10 = 121;
//public const int F11 = 122;
//public const int F12 = 123;
//public const int F13 = 124;
//public const int F14 = 125;
//public const int F15 = 126;
//public const int F16 = 127;
public const int A = 65;
//public const int B = 66;
public const int C = 67;
//public const int D = 68;
//public const int E = 69;
public const int F = 70;
//public const int G = 71;
//public const int H = 72;
//public const int I = 73;
//public const int J = 74;
//public const int K = 75;
//public const int L = 76;
public const int M = 77;
public const int N = 78;
//public const int O = 79;
public const int P = 80;
//public const int Q = 81;
//public const int R = 82;
//public const int S = 83;
//public const int T = 84;
//public const int U = 85;
public const int V = 86;
//public const int W = 87;
//public const int X = 88;
//public const int Y = 89;
//public const int Z = 90;
}
[/code]
Việc sử dụng hết sức đơn giản, các bạn hãy thử xem - tạo cho ứng dụng của mình những phím tắt nếu cần thiết.
[code language="csharp"]
class UsingHookKey
{
KeyboardListener KListener;
public UsingHookKey()
{
KListener = new KeyboardListener();
KListener.KeyDown += KListener_KeyDown;
}
void KListener_KeyDown(object sender, RawKeyEventArgs args)
{
switch (args.VKCode)
{
case VKCodeConst.N: //Key N
break;
case VKCodeConst.P: //Key P
break;
case VKCodeConst.V: //Key V
break;
}
}
}
[/code]
Chúc các bạn thành công!
Phạm Tuân
Thứ Năm, 1 tháng 1, 2015
LOG4NET là một thư viện mã nguồn mở cho phép chúng ta tạo ra một hoặc nhiều tập tin log, kiểu log cũng như nội dung log một cách linh hoạt và thuận tiện. Ngoài ra, Log4net còn có thể thay đổi trạng thái log lúc ứng dụng đang chạy mà không cần ngừng chương trình. Bên cạnh đó, khi sử dụng log4net sẽ không ảnh hưởng đáng kể đến performance của ứng dụng và điều quan trọng của một thư viện nguồn mở đó là chúng ta có thể tùy ý phát triển theo nhu cầu của cá nhân hoặc tập thể miễn là phù hợp giấy phép mã nguồn mở.
Để hiểu được tầm quan trọng của việc log, chúng ta có một tình huống như sau; Cty chúng tôi đang phát triển một ứng dụng và đã đến kỳ hạn phải triển khai cho người dùng, dĩ nhiên do dự án có độ trễ vì thế chúng tôi không thể đảm bảo rằng các tính năng cũng như toàn bộ hệ thống của chúng tôi hoạt động tốt, ngoài ra có một số lỗi mà chỉ khi được triển khai thực tế mới phát hiện. Vậy người dùng gặp lỗi khi sử dụng, mà vì khoản cách, vì bảo mật nên chúng tôi khổng thể tiếp cận khách hàng hoặc cài Visual trên máy của họ để DEBUG chưa kể nếu bạn chạy MultiThread thì rất khó Debug - chỉ mơ hồ biết thông tin về Bug thông qua một người không phải kỹ thuật viên thì phải mất khá lâu để cải thiện được tình hình. Thế thì việc tạo Log trong quá trình người dùng sử dụng thật sự là một giải pháp tốt - những con số biết nói. Tôi chỉ việc yêu cầu người dùng gửi cho chúng tôi file Log và họ sẽ sớm có bản vá lỗi cho vấn của họ.
Để sử dụng được Log4net các bạn cần chú ý đến các vấn đề sau đây:
* Tập tin cấu hình Log4net
Măc định tập tin này chính là tập tin cấu hình của một ứng dụng dạng [myApp].exe.config hay Web.config cho Asp.NET bạn cũng có thể tạo một file cấu hình khác
- Log ra những dạng nào: ConsoleAppender, FileAppender, RollingFileAppender, AdoNetAppender...
- Các file log có thuộc tính gì: file, appendToFile, maximumFileSize, ...
- Các thông tin cho một dòng log: layout, thread, date, level, ...
* Cách thức log
Là cách để thực hiện việc nhận cấu hình và tiến hành tạo log
- Các cấu hình log4net được lưu ở đâu
- Log những gì và level của log ra sao
[caption id="" align="aligncenter" width="640"]
I> Cấu trúc một file cấu hình log4net
Một tập tin cấu hình của log4net có cấu trúc đơn giản sẽ như dưới, trong đó phần quan trọng là các thẻ <appender> và thẻ <root>
[code language="xml"]
<!-- Đây là thư mục gốc của tập tin cấu hình của bạn -->
<configuration>
<!-- Phần này nên giữ nguyên -->
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<!-- Nội dung cấu hình -->
<log4net> <!-- Level 1 -->
<!-- Định nghĩa Các appender-->
<appender> <!-- Level 2 -->
<layout> <!-- Level 3 -->
<conversionPattern /> <!-- Level 4 -->
</layout>
<filter> <!-- Level 3 -->
</filter>
</appender>
<!-- Sử dụng các appender-->
<root> <!-- Level 2 -->
<level /> <!-- Level 3 -->
<appender-ref /> <!-- Level 3 -->
</root>
</configuration>
[/code]
Thẻ <appender>
Thẻ này quy định các thức thể hiện(lưu trữ) các dòng log, trong đó có 4 cách cơ bản thể hiện: ConsoleAppender, FileAppender, RollingFileAppender, AdoNetAppender, ... Cách hay dùng nhất là RollingFileAppender. Hãy để ý thẻ <layout>, xem bảng cuối bài để hiểu các thuộc tính.
RollingFileAppender: Tạo ra tập tin log, khi tập tin này quá giới hạn thì nó sẽ bị đổi tên và tạo tập tin mới để log, vậy chúng ta sẽ có nhiều tập tin log.
[code language="xml"]
<!--Log to file collection-->
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="mylogfile.txt" />
<pre> <appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
</appender>
[/code]
ConsoleAppender: log ra màng hình, chỉ hiển thị khi ứng dụng có của sổ Console
[code language="xml"]
<!--Log to Console window-->
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{ABSOLUTE} [%thread] %level %logger - %message%newline"/>
</layout>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
[/code]
FileAppender: tạo ra duy nhất một file log
[code language="xml"]
<!--Log to file-->
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="mylogfile.log" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>
</appender>
[/code]
AdoNetAppender: tạo ra các dòng log và lưu vào Database
[code language="xml"]
<!--Log to Database-->
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="100" />
<connectionType value="System.Data.SqlClient.SqlConnection,
System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="data source=[database server];
initial catalog=[database name];integrated security=false;
persist security info=True;User ID=[user];Password=[password]" />
<commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],
[Message],[Exception]) VALUES (@log_date, @thread, @log_level,
@logger, @message, @exception)" />
<parameter>
<parameterName value="@log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="@logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="@message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="@exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
[/code]
Trong đó ý nghĩa các thẻ con của thẻ appender như sau:
Layout
[code language="xml"]
<!-- Đây là thẻ mô tả cách hiển thị một dòng log -->
<layout type="log4net.Layout.PatternLayout">
<!-- Thẻ này là định dạng của một dòng log, ý nghĩa các ký tự xin tham khảo bảng cuối trang-->
<conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline"/>
</layout>
[/code]
Filter
[code language="xml"]
<!-- Thẻ này dùng để lọc log-->
<!-- Start-->
<!-- Chỉ log nếu dòng log có chữ test, chú ý rằng StringMatchFilter luôn đi kèm DenyAllFilter-->
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter"/>
<!-- End-->
<!-- Chỉ log trong một giới hạn level-->
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>
<!-- Start-->
<!-- Chỉ log level ERROR, chú ý rằng LevelMatchFilterluôn đi kèm DenyAllFilter-->
<filter type="log4net.Filter.LevelMatchFilter">
<levelToMatch value="ERROR"/>
</filter>
<filter type="log4net.Filter.DenyAllFilter"/>
<!-- End-->
<!-- Dùng chung với hai trường hợp kể trên-->
<filter type="log4net.Filter.DenyAllFilter"/>
[/code]
Xem chi tiết về filter tại bài config nâng cao
Level
Chúng ta có bảy level sau với mức cao nhất ở trên cùng của danh sách, khi bạn chọn một level để log thì các level từ đó trở lên đều được log:
-> OFF - không có gì được log
-> FATAL
-> ERROR
-> WARN
-> INFO
-> DEBUG
-> ALL - mọi thứ sẽ được log
[caption id="" align="aligncenter" width="451"]
Thẻ <Root>
Là thẻ quyết định level nào sẽ được log và các dạng log sẽ được sử dụng
[code language="xml"]
<root>
<level value="INFO"/> <!-- Chỉ log từ level INFO trở lên-->
<appender-ref ref="FileAppender"/> <!-- dùng FileAppender-->
<appender-ref ref="ConsoleAppender"/><!-- dùng ConsoleAppender-->
</root>
[/code]
II> Cách thức log
Khi đã hoàn tất việc tạo file config, chúng ta tiến hành chọn thời điểm và nội dung log phù hợp trong Code-behind, chúng ta có 4 bước như sau
B1-> Tải thư viện log4net.dll và add vào Project của bạn, nhớ Reference. Bạn có thể tải dll cả mã nguồn ở đây còn tôi thì tôi thích dùng Nuget với command sau
[code language="csharp"]
Install-Package log4net
[/code]
B2-> Hãy khai báo một biến static readonly toàn cục cho tất cả các class cần log, một ví dụ tổng quát bên dưới
[code language="csharp"]
class Program
{
//Coppy dòng code này cho tất cả các class
private static readonly log4net.ILog log =
log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static void Main(string[] args)
{
}
}
[/code]
B3-> Chỉ ra tập tin cấu hình của log4net, tôi có hai cách làm việc này
C1) Dán một trong hai dòng code sau vào dòng dưới cùng của tập tin AssemblyInfo.cs (tìm trong project của bạn)
[code language="csharp"]
//Dùng dòng này nếu tập tin cấu hình năm ở một nơi nào đó hoặc không phải tập tin cấu hình của ứng dụng
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "UsingLog4Net.xml", Watch = true)]
//Dùng dòng code này nếu tập tin cấu hình log4net chính là tập tin cấu hình của ứng dụng
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
[/code]
C2) Chỉ rõ tập tin cấu hình ở thân hàm mà bạn nghĩ hàm này sẽ được gọi đầu tiên khi ứng dụng được khởi động hoặc ít ra là trước khi tiến hành log bất kỳ thứ gì. Tôi nghĩ đó là hàm Main cho một ứng dụng Console
[code language="csharp"]
class Program
{
private static readonly log4net.ILog log =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static void Main(string[] args)
{
//Chỉ được dùng một trong hai dòng code sau
//Dòng này khi dùng một tập tin cấu hình bất kỳ
XmlConfigurator.Configure(new FileInfo("UsingLog4Net.xml"));
//Dòng này khi dùng tập tin cấu hình của ứng dụng
XmlConfigurator.Configure();
Console.ReadLine();
}
}
[/code]
B4-> Công việc còn lại là log nơi nào bạn nghĩ là cần thiết, tùy thuộc vào ý đồ mà chọn level phù hợp
[code language="csharp"]
class Program
{
private static readonly log4net.ILog log =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static void Main(string[] args)
{
XmlConfigurator.Configure(new FileInfo("UsingLog4Net.xml"));
log.Info("Application started");
try
{
int value = int.Parse("Tuân");
}
catch (Exception e)
{
log.ErrorFormat("Error: {0}",e.Message);
}
Console.ReadLine();
}
}
[/code]
Trên đây là tất cả những gì cở bản nhất để tiến hành tạo log cho một ứng dụng C#, mời bạn đọc tải về ví dụ tại đây. Ngoài ra bạn cũng có thể tham khảo AppDomain Logging và log4Net and SQLite
Dưới đây là ý nghĩa các giá trị thông tin cho một dòng log được cấu hình trong layout, tôi xin phép trích nguyên văn để các bạn dùng cho chính xác:
Conversion Patterns
As I mentioned above, the conversion pattern entry is used for the pattern layout to tell the appender how to store the information. There are many different keywords that can be used in these patterns, as well as string literals. Here I will specify what I think are the most useful and important ones. The full list can be found in the log4net documentation.
%date- Outputs the date using the local time zone information. This date can be formatted using the curly braces and a layout pattern such as %date{MMMM dd, yyyy HH:mm:ss, fff} to output the value of "January 01, 2011 14:15:43, 767". However, it is suggested that you use one of the log4net date formatters (ABSOLUTE, DATE, or ISO8601) since they offer better performance.%utcdate- This is the same as the%datemodifier, but it outputs in universal time. The modifiers for date/time all work the same way.%exception- If an exception is passed in, it will be entered and a new line will be placed after the exception. If no exception is passed in, this entry will be ignored and no new line will be put in. This is usually placed at the end of a log entry, and usually a new line is placed before the exception as well.%level- This is the level you specified for the event (DEBUG, INFO, WARN, etc.).%message- This is the message you passed into the log event.%newline- This is a new line entry. Based upon the platform you are using the application on, this will be translated into the appropriate new line character(s). This is the preferred method to enter a new line and it has no performance problems compared to the platform-specific operators.%timestamp- This is the number of milliseconds since the start of the application.%thread- This will give you the name of the thread that the entry was made on (or the number if the thread is not named).
Beyond these are a few more that can be very useful but should be used with caution. They have negative performance implications and should be used with caution. The list includes:
%identity- This is the user name of the current user using thePrincipal.Identity.Namemethod.%location- Especially useful if you are running in Debug mode, this tells you where the log method was called (line number, method, etc.). However, the amount of information will decrease as you operate in Release mode depending on what the system can access from the compiled code.%line- This is the line number of the code entry (see the note above on the location issues).%method- This is the method that calls the log entry (see the note above on the location issues).%username- This outputs the value of theWindowsIdentityproperty.
You may notice that some config files have letters instead of names. These have been depreciated in favor of whole word entries like I have specified above. Also, while I won't cover it in depth here, note that each of these entries can be formatted to fit a certain width. Spaces can be added (to either side) and values can be truncated in order to fit inside of fixed-width columns. The basic syntax is to place a numeric value or values between the % sign and the name. Here are the modifiers:
X- Specifies the minimum number of characters. Anything that has fewer characters will have spaces placed on the left of the value to equal 20 characters including the message. For example,%10messagewill give you "hi".-X- Same as above, only the spaces will be placed on the right. For example,%-10messagewill give you "hi"..X- Specifies the maximum number of characters. The important thing to note is that this will truncate the beginning of the string, not the end. For example,%.10messagewill give me "rror entry" if the string passed in was "Error entry".
You can put all of this together with something like this: "%10.20message", which would specify that if the message isn't ten characters long, put spaces on the left to fill it out to ten characters, but if the message is more than 20 characters long, cut off the beginning to make it only 20 characters.
Search
Popular Posts
-
Để dễ hiểu, các bạn hãy hình dung rằng: cùng một tập tin thực thi *.EXE nhưng bạn DoubleClick 10 lần cách quãng vào nó thì chuyện gì sẽ sảy ...
-
Phong cách thiết kế Material Design nhắm đến những đường nét đơn giản, sử dụng nhiều mảng màu đậm nổi bật, các đối tượng đồ họa trong giao ...
-
LOG4NET là một thư viện mã nguồn mở cho phép chúng ta tạo ra một hoặc nhiều tập tin log, kiểu log cũng như nội dung log một cách linh hoạt v...
-
ReSharper là một công cụ Add-on cho Visual Studio, nó giúp lập trình viên tốn ít thời gian cho việc xem xét định dạng code, phong cách code,...
-
Hôm nay chúng ta sẽ tìm hiểu cách tạo ra các menu chuột phải trên ứng dụng WPF của mình. Ở bài này chúng ta xoay quanh việc tìm hiểu cách tạ...
-
Hôm nay tôi sẽ giới thiệu với các bạn một Framework đến từ Microsoft - framework này không quá lớn nhưng là khá mạnh khi các bạn làm việc vớ...
-
Một khía cạnh hết sức quan trọng trong WPF đó là Trigger - Trigger giúp nắm bắt được mọi thay đổi trong một hệ thống GUI của WPF. Nhờ có Tr...
-
Như ở bài trước tôi đã giới thiệu về caliburn.mocro, bài tiếp theo này tôi sẽ hướng dẫn các bạn về cách tạo một project WPF hợp chuẩn mô hìn...
-
Khi các bạn làm một dự án khá lớn, nhu cầu tạo ra các Custom Control là không tránh khỏi, trong WinForm hay WPF đều cho phép chúng ta tạo r...
-
Hôm nay tôi sẽ giới thiệu đến các bạn một thư viện mã nguồn mở, nó là gì? Đơn giản là một thư viện với TabControl được Custom lại với nhiều ...





