I want to use a pool of non-thread-safe encoder instances across multiple concurrent ASP.NET requests, without reinitializing them. If I didn't care about initialization costs, I'd do this:
public class ResultController {
public JsonResult GetResults() {
List<MyViewModel> items = new List<MyViewModel>();
// It obviously does more than this in real life
for(id = 0; id < 1000; id++) {
items.Add(new MyViewModel(id));
}
return Json(items);
}
}
public class MyViewModel() {
public string CodedId { get; set; }
public MyViewModel(int id) {
// This "new" is the concern
CodedId = new ExpensiveToInitializeCodec().InexpensiveEncode(id);
}
}
Works fine. All locals, no worries about threading, and no-one outside my model needs to understand that the value is presented encoded. However, a quick performance test shows that each initialization takes about .129ms, while an encode itself takes less than .006ms. (FYI, the codec is TripleDESCryptoServiceProvider).
I want to limit that initialization cost without passing pre-initialized objects around (such as into constructors to improve performance, but breaking separation of concerns). Here's what I currently do, and it obviously gets messy outside this simple example:
public class ResultController {
public JsonResult GetResults() {
List<MyViewModel> items = new List<MyViewModel>();
ExpensiveToInitializeCodec codec = new ExpensiveToInitializeCodec();
for(id = 0; id < 1000; id++) {
items.Add(new MyViewModel(id, codec));
}
return Json(items);
}
}
public class MyViewModel() {
public string CodedId { get; set; }
public MyViewModel(int id, ExpensiveToInitializeCodec codec) {
CodedId = codec.InexpensiveEncode(id);
}
}
I could leverage the well-known ASP.NET per-request cache pattern:
public class MyViewModel() {
public string CodedId { get; set; }
public MyViewModel(int id) {
CodedId = ExpensiveToInitializeCodec.Get().InexpensiveEncode(id);
}
}
public class ExpensiveToInitializeCodec {
public static ExpensiveToInitializeCodec Get() {
ExpensiveToInitializeCodec codec = HttpContext.Current.Items["codec"];
if (codec == null) {
codec = new ExpensiveToInitializeCodec();
HttpContext.Current.Items["codec"] = codec;
}
return codec;
}
}
But that's still wasteful to run in a tight loop: How much computation is behind a HttpContext.Current call?
It also seemed like a per-thread solution may be more precise than a per-request solution. Any suggestions that are compatible with an ASP.NET request?
Outside of ASP but still in the .NET space, one person's answer was ThreadStatic: Using ThreadStatic to replace expensive locals -- good idea? . However, http://blog.idm.fr/2010/03/aspnet-thread-agility-or-why-threadstatic-should-not-be-used.html apparently precludes that solution in ASP.NET. A similar question to mine Is there any way to imitate ThreadStatic for use with HttpContext.Current.Items? went unanswered.
EDIT: It looks like I may be able to use ThreadStatic if I make sure my codec usage isn't interleaved with I/O operations, but I'm not sure how safe that is.
I'm reaching now, but some other approaches I've thought of include 1) providing a custom serialization/deserialization for items with an EncodedAttribute, 2) implementing my own static TripleDES single-block encryptor with no instance initialization overhead, 3) against my preferences, passing an externally-initialized encryptor into the constructor for each item, 4) implementing an IEncryptable interface and re-enumerating the items after the results are populated, 5) perform all encryption external to the viewmodel, implemented everywhere the viewmodel is used.